Decorate-sort-undecorate idiom: Difference between revisions

m
m (→‎{{header|Phix}}: added "reverse()" output, to match JavaScript)
 
(41 intermediate revisions by 19 users not shown)
Line 1:
{{task}}
 
[[Category:Sorting]]
 
;Introduction
Line 153 ⟶ 155:
<pre>[ "a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy" ]</pre>
 
=={{header|FactorC}}==
C needs to use temporary arrays for the decorate and undecorate stages.
{{works with|Factor 0.99 build 2207}}
<syntaxhighlight lang="c">#include <stdio.h>
The easiest way is to define your key function as a memoized word. Memoized words store input/output pairs and simply fetch the outputs of inputs they've seen before. Then using the usual <code>sort-by</code> combinator won't recalculate any extraneous lengths.
#include <stdlib.h>
<syntaxhighlight lang="factor">
#include <string.h>
USING: prettyprint sequences sorting ;
 
typedef struct {
MEMO: length* ( seq -- n ) length ;
const char *word;
size_t key;
} wordkey;
 
int compare(const void* p1, const void* p2) {
{ "Rosetta" "Code" "is" "a" "programming" "chrestomathy" "site" }
const int ip1 = ((wordkey *)p1)->key;
[ length* ] sort-by .
const int ip2 = ((wordkey *)p2)->key;
return (ip1 < ip2) ? -1 : ((ip1 > ip2) ? 1 : 0);
}
 
size_t length(const char *s) { return strlen(s); }
 
void sortWords(char **words, size_t le, size_t (*f)(const char* s)) {
int i;
char words2[le][15]; // to store the sorted array
 
/* decorate */
wordkey wordkeys[le];
for (i = 0; i < le; ++i) {
wordkeys[i] = (wordkey){words[i], f(words[i])};
}
 
/* sort (unstable) */
qsort(wordkeys, le, sizeof(wordkey), compare);
 
/* undecorate and print */
printf("[");
for (i = 0; i < le; ++i) {
sprintf(words2[i], "\"%s\"", wordkeys[i].word);
printf("%s, ", words2[i]);
}
printf("\b\b]\n");
}
 
int main() {
char *words[7] = {'\0'};
words[0] = "Rosetta";
words[1] = "Code";
words[2] = "is";
words[3] = "a";
words[4] = "programming";
words[5] = "chrestomathy";
words[6] = "site";
sortWords(words, 7, length);
return 0;
}</syntaxhighlight>
 
{{out}}
<pre>
["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
</pre>
 
=={{header|C sharp|C#}}==
Linq makes this very easy. We can do this using 2 different syntaxes:
<syntaxhighlight lang="csharp">
public static IEnumerable<T> Schwartzian1<T, TKey>(IEnumerable<T> source, Func<T, TKey> decorator) =>
source.Select(item => (item, key: decorator(item)))
.OrderBy(tuple => tuple.key)
.Select(tuple => tuple.item);
 
public static IEnumerable<T> Schwartzian2<T, TKey>(IEnumerable<T> source, Func<T, TKey> decorator) =>
from item in source
let key = decorator(item)
orderby key
select item;
 
//Call:
string[] array = {"Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site"};
Console.WriteLine(string.Join(" ", Schwartzian1(array, i => i.Length)));
</syntaxhighlight>
{{out}}
<pre>
a is Code site Rosetta programming chrestomathy
</pre>
 
=={{header|C++}}==
A more direct implementation would be:
<syntaxhighlight lang="cpp">#include <algorithm>
#include <iostream>
#include <type_traits>
#include <vector>
 
template <typename Iterator, typename Function>
void decorate_sort_undecorate(Iterator begin, Iterator end, Function f) {
using ValueType = typename std::iterator_traits<Iterator>::value_type;
using KeyType = std::invoke_result_t<Function, ValueType>;
using KeyValue = std::pair<KeyType, ValueType>;
std::vector<KeyValue> tmp;
tmp.reserve(std::distance(begin, end));
std::transform(begin, end, std::back_inserter(tmp), [&f](ValueType& v) {
return std::make_pair(f(v), std::move(v));
});
std::sort(tmp.begin(), tmp.end(), [](const KeyValue& a, const KeyValue& b) {
return a.first < b.first;
});
std::transform(tmp.begin(), tmp.end(), begin,
[](KeyValue& p) { return std::move(p.second); });
}
 
int main() {
std::string test[] = {"Rosetta", "Code", "is", "a",
"programming", "chrestomathy", "site"};
decorate_sort_undecorate(std::begin(test), std::end(test),
[](const std::string& s) { return s.size(); });
for (const std::string& s : test)
std::cout << s << ' ';
std::cout << '\n';
}</syntaxhighlight>
 
{{out}}
<pre>
a is Code site Rosetta programming chrestomathy
</pre>
 
=={{header|Factor}}==
{{works with|Factor|0.99}}
<code>map-sort</code> employs the decorate-sort-undecorate idiom, while <code>sort-by</code> does not.
<syntaxhighlight lang="factor">
USING: assocs prettyprint sequences sorting.extras ;
 
{ "Rosetta" "Code" "is" "a" "programming" "chrestomathy" "site" }
[ length ] zipmap-withsort .
[ last ] sort-by
keys .
</syntaxhighlight>
{{out}}
Line 241 ⟶ 351:
'''Solution'''
 
Let us create aan listarray for testing:
 
[[File:Fōrmulæ - Decorate-sort-undecorate idiom 01.png]]
Line 254 ⟶ 364:
 
[[File:Fōrmulæ - Decorate-sort-undecorate idiom 04.png]]
 
 
'''Schwartzian transform:''' The following is a solution using a "Schwartzian transform" idiom, and it produces identical results:
 
[[File:Fōrmulæ - Decorate-sort-undecorate idiom 05.png]]
 
=={{header|Go}}==
Go needs to use a temporary slice for the decoration part.
<syntaxhighlight lang="go">package main
 
import (
"fmt"
"sort"
)
 
type wordkey struct {
word string
key int
}
 
func sortWords(words []string, f func(s string) int) {
var le = len(words)
 
// decorate
wordkeys := make([]wordkey, le)
for i := 0; i < le; i++ {
wordkeys[i] = wordkey{words[i], f(words[i])}
}
 
// sort (stable)
sort.SliceStable(wordkeys, func(i, j int) bool {
return wordkeys[i].key < wordkeys[j].key
})
 
// undecorate (mutates original slice)
for i := 0; i < le; i++ {
words[i] = "\"" + wordkeys[i].word + "\""
}
 
fmt.Println(words)
}
 
func main() {
words := []string{"Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site"}
length := func(s string) int { return len(s) }
sortWords(words, length)
}</syntaxhighlight>
 
{{out}}
<pre>
["a" "is" "Code" "site" "Rosetta" "programming" "chrestomathy"]
</pre>
 
=={{header|Haskell}}==
Line 312 ⟶ 470:
"programming"
"chrestomathy"</pre>
 
=={{header|J}}==
J's native sort primitive always sorts on a key (which might be the list itself), such that each element of the key is calculated only once. This corresponds to APL's grade up primitive (though comparing dates on this approach vs. lisp's approach seems problematic due to deficiencies in historical evidence).
 
Thus, for example:
 
<syntaxhighlight lang=J> >(/: #@>) ;:'Rosetta Code is a programming chrestomathy site'
a
is
Code
site
Rosetta
programming
chrestomathy
</syntaxhighlight>
 
=={{header|Java}}==
<syntaxhighlight lang="java">
import java.util.AbstractMap;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
 
public final class DecorateSortUndecorateIdiom {
 
public static void main(String[] args) {
List<String> list = List.of( "Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site" );
System.out.println(schwartzian(list, s -> s.length()));
}
/**
* Return a sorted list using the Schwartzian Transform
* which guarantees minimal use of the key extractor function.
*
* Use this method when the key extractor function is an expensive operation.
*/
private static <T, R extends Comparable<R>> List<T> schwartzian(List<T> list, Function<T, R> function) {
return list.stream().map( s -> new AbstractMap.SimpleEntry<T, R>(s, function.apply(s)) )
.sorted( (one, two) -> one.getValue().compareTo(two.getValue()) )
.map( p -> p.getKey() )
.collect(Collectors.toList());
}
 
}
</syntaxhighlight>
{{ out }}
<pre>
[a, is, Code, site, Rosetta, programming, chrestomathy]
</pre>
 
=={{header|JavaScript}}==
Line 487 ⟶ 694:
"programming"
"chrestomathy"
</pre>
 
=={{header|Kotlin}}==
<syntaxhighlight lang="kotlin">
fun main() {
val list = listOf("Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site")
println(list.sortedBySchwartzian(String::length))
}
 
/**
* Returns a sorted list using the Schwartzian Transform which guarantees minimal use of the
* key extractor function. Use when the key extractor function is an expensive operation.
*/
fun <T, R: Comparable<R>> Collection<T>.sortedBySchwartzian(keyFn: (T) -> R): List<T> =
this.map { it to keyFn(it) }
.sortedBy { it.second }
.map { it.first }
</syntaxhighlight>
 
Output:
 
<pre>
[a, is, Code, site, Rosetta, programming, chrestomathy]
</pre>
 
=={{header|Lua}}==
Lua's 'table.sort' accepts a custom compare function as a second argument.
<syntaxhighlight lang="lua">-- Decorate, sort, undecorate function
function dsu (tab, keyFunc)
keyFunc = keyFunc or function (a, b) return a[2] < b[2] end
for key, value in pairs(tab) do
tab[key] = {value, #value}
end
table.sort(tab, keyFunc)
for key, value in pairs(tab) do
tab[key] = value[1]
end
return tab
end
 
-- Use default sort order by not specifying a key function
local list = {"Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site"}
print(unpack(dsu(list)))
 
-- Create a custom key function and pass it as an argument
function descendingOrder (a, b)
return a[2] > b[2]
end
print(unpack(dsu(list, descendingOrder)))</syntaxhighlight>
{{out}}
<pre>a is site Code Rosetta programming chrestomathy
chrestomathy programming Rosetta Code site is a</pre>
 
=={{header|Nim}}==
In Nim, there are several ways to sort a list either by creating an explicit temporary list or using a map-sort-map idiom.
 
The easiest way to sort a list of words by word length consists to use the “sortedByIt” template which eliminates the need to create a decorated list. But this is not what is required in this task.
 
Here is one way to sort the words by length using the decorate-sort-decorate idiom:
 
<syntaxhighlight lang="Nim">import std/[algorithm, sequtils]
 
let wordList = ["Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site"]
 
echo wordList.mapIt((value: it, length: it.len)).sortedByIt(it.length).mapIt(it.value)
</syntaxhighlight>
 
{{out}}
<pre>@["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
</pre>
 
Note that words with same length will appear in the original list order. If we want them to appear in
alphabetical order, we need to sort the list alphabetically first. As Nim sorting algorithm is stable,
the order of items with same key value (i.e. length in this example) is preserved when sorting.
 
Of course, it is also possible to define a comparison function to sort by length then alphabetically and use it when sorting.
 
=={{header|Nu}}==
<syntaxhighlight lang="nu">
def 'sort by key' [keyfunc] {
( each {|v| {k: ($v | do $keyfunc ), v: $v}}
| sort-by k
| each {get v} )
}
 
"Rosetta Code is a programming chrestomathy site" | split words | sort by key {str length}
</syntaxhighlight>
{{out}}
<pre>
╭───┬──────────────╮
│ 0 │ a │
│ 1 │ is │
│ 2 │ Code │
│ 3 │ site │
│ 4 │ Rosetta │
│ 5 │ programming │
│ 6 │ chrestomathy │
╰───┴──────────────╯
</pre>
 
=={{header|Perl}}==
<syntaxhighlight lang="perl">#!/usr/bin/perl
 
use strict; # https://rosettacode.org/wiki/Decorate-sort-undecorate_idiom
use warnings;
use List::AllUtils qw( nsort_by );
 
my @list = ("Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site");
print "@list\n";
 
my @sortedlist = nsort_by { length } @list;
print "@sortedlist\n";</syntaxhighlight>
{{out}}
<pre>
Rosetta Code is a programming chrestomathy site
a is Code site Rosetta programming chrestomathy
</pre>
 
Line 497 ⟶ 820:
<span style="color: #0000FF;">?</span><span style="color: #000000;">sort_by</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">split</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Rosetta Code is a programming chrestomathy site"</span><span style="color: #0000FF;">),</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">)</span>
<!--</syntaxhighlight>-->
Technically that does not decorate/undecorate and instead uses an anonymous parallel array or two, but it does only calculate each length once.
{{out}}
<pre>
Line 583 ⟶ 907:
programming
chrestomathy</pre>
 
=={{header|Quackery}}==
 
<syntaxhighlight lang="Quackery"> [ ]'[ temp put
[] swap witheach
[ dup temp share do
dip nested join
nested join ]
temp release ] is decoratewith ( [ --> [ )
 
[ [] swap witheach
[ 0 peek nested join ] ] is undecorate ( [ --> [ )
 
$ "Rosetta Code is a programming chrestomathy site" nest$
 
decoratewith size
sortwith [ dip [ 1 peek ] 1 peek > ]
undecorate
witheach [ echo$ sp ]</syntaxhighlight>
 
{{out}}
 
<pre>a is Code site Rosetta programming chrestomathy</pre>
 
=={{header|Raku}}==
Line 599 ⟶ 946:
 
More complicated transforms may ''require'' an explicit schwartzian transform routine. An example of where an explicit transform is desirable is the <code>schwartzian()</code> routine in the [[P-value_correction#Raku|Raku entry for the P-value_correction task]].
=={{header|RPL}}==
This implementation accepts the key function as a callback but uses local named variables.
≪ → seq func
≪ { }
1 seq SIZE '''FOR''' j
seq j GET
DUP func EVAL
2 →LIST 1 →LIST +
'''NEXT'''
≫ ≫ '<span style="color:blue">DECOR</span>' STO
≪ '''IF''' DUP SIZE 2 ≥ '''THEN'''
LIST→ → len
≪ len 1 '''FOR''' n
1 n 1 - '''START'''
DUP2 2 GET SWAP 2 GET
'''IF''' < '''THEN''' SWAP '''END'''
n ROLLD
'''NEXT''' n ROLLD
-1 '''STEP''' len →LIST
≫ '''END'''
≫ '<span style="color:blue">KSORT</span>' STO
≪ → seq
≪ { }
1 seq SIZE '''FOR''' j
seq j GET 1 GET +
'''NEXT'''
≫ ≫ '<span style="color:blue">UNDECOR</span>' STO
 
{ "Rosetta" "Code" "is" "a" "programming" "chrestomathy" "site" } ≪ SIZE ≫ <span style="color:blue">DECOR KSORT UNDECOR</span>
{{out}}
<pre>
1: {"a" "is" "Code" "site" "Rosetta" "programming" "chrestomathy" }
</pre>
 
=={{header|Ruby}}==
Arrays have a <code>sort_by</code> method which does a Schwartzian transform.
<syntaxhighlight lang="ruby">p "Rosetta Code is a programming chrestomathy site".split.sort_by(&:size)
# sort by word-size, then lexical:
str = "Rosetta Code is a programming chrestomathy site seven extra words added to this demo"
p str.split.sort_by{|word| [word.size, word]}
</syntaxhighlight>
{{out}}
<pre>["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
["a", "is", "to", "Code", "demo", "site", "this", "added", "extra", "seven", "words", "Rosetta", "programming", "chrestomathy"]
</pre>
 
=={{header|Rust}}==
<syntaxhighlight lang="rust">use itertools::sorted;
 
// sort by decorator using builtin function sort_by_cached_key
fn sort_by_cached(mut arr: Vec<&str>, decorator: impl Fn(&str) -> usize) -> Vec<&str> {
arr.sort_by_cached_key(|t| decorator(*t));
return arr;
}
 
// sort by decorator using Schwartzian transform
fn sort_by_decorator(arr: Vec<&str>, decorator: impl Fn(&str) -> usize) -> Vec<&str> {
return sorted(
arr
.iter()
.map(|e| (decorator(e), *e))
.collect::<Vec<(usize, &str)>>()
)
.map(|e| e.1)
.collect::<Vec<&str>>();
}
 
fn main() {
let arr = Vec::from(["Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site"]);
println!("{:?} => ", arr);
println!(
" cached: {:?}",
sort_by_cached(arr.clone(), |x| x.len())
);
println!(
" transform: {:?}",
sort_by_decorator(arr, |x| x.len())
);
}
</syntaxhighlight>{{out}}
<pre>
["Rosetta", "Code", "is", "a", "programming", "chrestomathy", "site"] =>
cached: ["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
transform: ["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
</pre>
 
=={{header|Sidef}}==
<syntaxhighlight lang="ruby">var str = "Rosetta Code is a programming chrestomathy site"
 
# Built-in
say str.split.sort_by{.len}
 
# Same thing explicitly
say str.split.map {|w| [w, w.len] }.sort{|a,b| a[1] <=> b[1] }.map { _[0] }
 
# Sort by word length, then lexical:
var str2 = str+" seven extra words added to this demo"
say str2.split.sort_by{|word| [word.len, word] }</syntaxhighlight>
{{out}}
<pre>
["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
["a", "is", "Code", "site", "Rosetta", "programming", "chrestomathy"]
["a", "is", "to", "Code", "demo", "site", "this", "added", "extra", "seven", "words", "Rosetta", "programming", "chrestomathy"]
</pre>
 
=={{header|Wren}}==
Line 605 ⟶ 1,058:
 
It's not specified how words of equal length are to be sorted. The standard sort() method used here (quicksort under the hood) is unstable and so the sort order for such words is not guaranteed.
<syntaxhighlight lang="ecmascriptwren">var schwartzian = Fn.new { |a, f|
System.print(a.map { |e| [e, f.call(e)] } // decorate
.toList
Line 618 ⟶ 1,071:
{{out}}
<pre>["a", "is", "site", "Code", "Rosetta", "programming", "chrestomathy"]</pre>
 
=={{header|XPL0}}==
<syntaxhighlight lang "XPL0">
include xpllib; \for StrLen and StrSort
int A, Len, I;
char B;
[A:= [" Rosetta"," Code"," is"," a"," programming"," chrestomathy"," site"];
Len:= 7;
B:= A; \to access bytes instead of integers
for I:= 0 to Len-1 do \decorate
B(I,0):= StrLen(A(I))-1;
StrSort(A, Len);
for I:= 0 to Len-1 do
[B(I,0):= ^ ; \undecorate
Text(0, A(I));
];
]</syntaxhighlight>
{{out}}
<pre>
a is Code site Rosetta programming chrestomathy</pre>
2,120

edits