package Acme::CPANModules::OrderedHash;

use strict;

our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2023-10-05'; # DATE
our $DIST = 'Acme-CPANModules-OrderedHash'; # DIST
our $VERSION = '0.001'; # VERSION

our $LIST = {
    summary => "List of modules that provide ordered hash data type",
    description => <<'_',

When you ask a Perl's hash for the list of keys, the answer comes back
unordered. In fact, Perl explicitly randomizes the order of keys it returns
everytime. The random ordering is a (security) feature, not a bug. However,
sometimes you want to know the order of insertion. These modules provide you
with an ordered hash; most of them implement it by recording the order of
insertion of keys in an additional array.

Other related modules:

<pm:Tie::SortHash> - will automatically sort keys when you call `keys()`,
`values()`, `each()`. But this module does not maintain insertion order.

_
    entries => [

        {
            module => 'Tie::IxHash',
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                tie my %hash, "Tie::IxHash";
                for (1..$numkeys) { $hash{"key$_"} = $_ }

                if ($op eq 'delete') {
                    for (1..$numkeys) { delete $hash{"key$_"} }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = keys %hash }
                }
            },
        },

        {
            module => 'Hash::Ordered',
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                my $hash = Hash::Ordered->new;
                for (1..$numkeys) { $hash->set("key$_" => $_) }

                if ($op eq 'delete') {
                    for (1..$numkeys) { $hash->delete("key$_") }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = $hash->keys }
                }
            },
        },

        {
            module => 'Tie::Hash::Indexed',
            description => <<'MARKDOWN',

Provides two interfaces: tied hash and OO.

MARKDOWN
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                tie my %hash, "Tie::Hash::Indexed";
                for (1..$numkeys) { $hash{"key$_"} = $_ }

                if ($op eq 'delete') {
                    for (1..$numkeys) { delete $hash{"key$_"} }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = keys %hash }
                }
            },
        },

        {
            module => 'Tie::LLHash',
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                tie my %hash, "Tie::LLHash";
                for (1..$numkeys) { (tied %hash)->insert("key$_" => $_) }

                if ($op eq 'delete') {
                    for (1..$numkeys) { delete $hash{"key$_"} }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = keys %hash }
                }
            },
        },

        {
            module => 'Tie::StoredOrderHash',
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                tie my %hash, "Tie::StoredOrderHash";
                for (1..$numkeys) { $hash{"key$_"} = $_ }

                if ($op eq 'delete') {
                    for (1..$numkeys) { delete $hash{"key$_"} }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = keys %hash }
                }
            },
        },

        {
            module => 'Array::OrdHash',
            description => <<'_',

Provide something closest to PHP's associative array, where you can refer
elements by key or by numeric index, and insertion order is remembered.

_
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                my $hash = Array::OrdHash->new;
                for (1..$numkeys) { $hash->{"key$_"} = $_ }

                if ($op eq 'delete') {
                    for (1..$numkeys) { delete $hash->{"key$_"} }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = keys %$hash }
                }
            },
        },

        {
            module => 'List::Unique::DeterministicOrder',
            description => <<'MARKDOWN',

Provide a list, not hash.

MARKDOWN
            bench_code => sub {
                my ($op, $numkeys, $numrep) = @_;

                my $hash = List::Unique::DeterministicOrder->new(data=>[]);
                for (1..$numkeys) { $hash->push("key$_") }

                if ($op eq 'delete') {
                    for (1..$numkeys) { $hash->delete("key$_") }
                } elsif ($op eq 'keys') {
                    for (1..$numrep) { my @keys = $hash->keys }
                }
            },
        },
    ],

    bench_datasets => [
        {name=>'insert 1000 pairs', argv => ['insert', 1000]},
        {name=>'insert 1000 pairs + delete', argv => ['delete', 1000]},
        {name=>'insert 1000 pairs + return keys 100 times', argv => ['keys', 1000, 100]},
        # TODO: iterating
    ],
};

1;
# ABSTRACT: List of modules that provide ordered hash data type

__END__

=pod

=encoding UTF-8

=head1 NAME

Acme::CPANModules::OrderedHash - List of modules that provide ordered hash data type

=head1 VERSION

This document describes version 0.001 of Acme::CPANModules::OrderedHash (from Perl distribution Acme-CPANModules-OrderedHash), released on 2023-10-05.

=head1 SYNOPSIS

To run benchmark with default option:

 % bencher --cpanmodules-module OrderedHash

To run module startup overhead benchmark:

 % bencher --module-startup --cpanmodules-module OrderedHash

For more options (dump scenario, list/include/exclude/add participants, list/include/exclude/add datasets, etc), see L<bencher> or run C<bencher --help>.

=head1 DESCRIPTION

When you ask a Perl's hash for the list of keys, the answer comes back
unordered. In fact, Perl explicitly randomizes the order of keys it returns
everytime. The random ordering is a (security) feature, not a bug. However,
sometimes you want to know the order of insertion. These modules provide you
with an ordered hash; most of them implement it by recording the order of
insertion of keys in an additional array.

Other related modules:

L<Tie::SortHash> - will automatically sort keys when you call C<keys()>,
C<values()>, C<each()>. But this module does not maintain insertion order.

=head1 ACME::CPANMODULES ENTRIES

=over

=item L<Tie::IxHash>

Author: L<CHORNY|https://metacpan.org/author/CHORNY>

=item L<Hash::Ordered>

Author: L<DAGOLDEN|https://metacpan.org/author/DAGOLDEN>

=item L<Tie::Hash::Indexed>

Author: L<MHX|https://metacpan.org/author/MHX>

Provides two interfaces: tied hash and OO.


=item L<Tie::LLHash>

Author: L<XAERXESS|https://metacpan.org/author/XAERXESS>

=item L<Tie::StoredOrderHash>

Author: L<TFM|https://metacpan.org/author/TFM>

=item L<Array::OrdHash>

Author: L<WOWASURIN|https://metacpan.org/author/WOWASURIN>

Provide something closest to PHP's associative array, where you can refer
elements by key or by numeric index, and insertion order is remembered.


=item L<List::Unique::DeterministicOrder>

Author: L<SLAFFAN|https://metacpan.org/author/SLAFFAN>

Provide a list, not hash.


=back

=head1 BENCHMARKED MODULES

Version numbers shown below are the versions used when running the sample benchmark.

L<Tie::IxHash> 1.23

L<Hash::Ordered> 0.014

L<Tie::Hash::Indexed> 0.08

L<Tie::LLHash> 1.004

L<Tie::StoredOrderHash> 0.22

L<Array::OrdHash> 1.03

L<List::Unique::DeterministicOrder> 0.004

=head1 BENCHMARK PARTICIPANTS

=over

=item * Tie::IxHash (perl_code)

L<Tie::IxHash>



=item * Hash::Ordered (perl_code)

L<Hash::Ordered>



=item * Tie::Hash::Indexed (perl_code)

L<Tie::Hash::Indexed>



=item * Tie::LLHash (perl_code)

L<Tie::LLHash>



=item * Tie::StoredOrderHash (perl_code)

L<Tie::StoredOrderHash>



=item * Array::OrdHash (perl_code)

L<Array::OrdHash>



=item * List::Unique::DeterministicOrder (perl_code)

L<List::Unique::DeterministicOrder>



=back

=head1 BENCHMARK DATASETS

=over

=item * insert 1000 pairs

=item * insert 1000 pairs + delete

=item * insert 1000 pairs + return keys 100 times

=back

=head1 BENCHMARK SAMPLE RESULTS

=head2 Sample benchmark #1

Run on: perl: I<< v5.38.0 >>, CPU: I<< Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz (2 cores) >>, OS: I<< GNU/Linux Ubuntu version 20.04 >>, OS kernel: I<< Linux version 5.4.0-91-generic >>.

Benchmark command (default options):

 % bencher --cpanmodules-module OrderedHash

Result formatted as table (split, part 1 of 3):

 #table1#
 {dataset=>"insert 1000 pairs"}
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+
 | participant                      | rate (/s) | time (ms) | pct_faster_vs_slowest | pct_slower_vs_fastest |  errors   | samples |
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+
 | Tie::LLHash                      |       200 |      4    |                 0.00% |               333.85% |   0.00013 |      20 |
 | Tie::StoredOrderHash             |       350 |      2.9  |                41.84% |               205.88% | 7.5e-06   |      20 |
 | Array::OrdHash                   |       540 |      1.8  |               118.76% |                98.32% | 2.1e-06   |      20 |
 | Hash::Ordered                    |       600 |      2    |               129.15% |                89.33% | 8.7e-05   |      32 |
 | Tie::IxHash                      |       670 |      1.5  |               169.83% |                60.78% | 2.5e-06   |      20 |
 | Tie::Hash::Indexed               |       700 |      1    |               187.24% |                51.04% | 5.7e-05   |      20 |
 | List::Unique::DeterministicOrder |      1100 |      0.93 |               333.85% |                 0.00% | 2.3e-06   |      20 |
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+

The above result formatted in L<Benchmark.pm|Benchmark> style:

          Rate   T:L   T:S   H:O   A:O   T:I  TH:I  LU:D 
  T:L    200/s    --  -27%  -50%  -55%  -62%  -75%  -76% 
  T:S    350/s   37%    --  -31%  -37%  -48%  -65%  -67% 
  H:O    600/s  100%   44%    --   -9%  -25%  -50%  -53% 
  A:O    540/s  122%   61%   11%    --  -16%  -44%  -48% 
  T:I    670/s  166%   93%   33%   19%    --  -33%  -38% 
  TH:I   700/s  300%  190%  100%   80%   50%    --   -6% 
  LU:D  1100/s  330%  211%  115%   93%   61%    7%    -- 
 
 Legends:
   A:O: participant=Array::OrdHash
   H:O: participant=Hash::Ordered
   LU:D: participant=List::Unique::DeterministicOrder
   T:I: participant=Tie::IxHash
   T:L: participant=Tie::LLHash
   T:S: participant=Tie::StoredOrderHash
   TH:I: participant=Tie::Hash::Indexed

The above result presented as chart:

#IMAGE: share/images/bencher-result-1.png|/tmp/dxS0EuAWVP/bencher-result-1.png

Result formatted as table (split, part 2 of 3):

 #table2#
 {dataset=>"insert 1000 pairs + delete"}
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+
 | participant                      | rate (/s) | time (ms) | pct_faster_vs_slowest | pct_slower_vs_fastest |  errors   | samples |
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+
 | Tie::IxHash                      |        15 |      67   |                 0.00% |              4003.59% |   0.00034 |      20 |
 | Tie::StoredOrderHash             |       200 |       6   |              1082.65% |               246.98% | 8.4e-05   |      21 |
 | Tie::LLHash                      |       200 |       5   |              1119.91% |               236.39% |   0.00013 |      20 |
 | Array::OrdHash                   |       270 |       3.8 |              1686.96% |               129.64% | 1.5e-05   |      20 |
 | Hash::Ordered                    |       300 |       3   |              2008.58% |                94.61% | 3.4e-05   |      22 |
 | Tie::Hash::Indexed               |       500 |       2   |              3188.02% |                24.80% | 6.7e-05   |      20 |
 | List::Unique::DeterministicOrder |       610 |       1.6 |              4003.59% |                 0.00% | 4.3e-06   |      20 |
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+

The above result formatted in L<Benchmark.pm|Benchmark> style:

         Rate    T:I   T:S   T:L   A:O   H:O  TH:I  LU:D 
  T:I    15/s     --  -91%  -92%  -94%  -95%  -97%  -97% 
  T:S   200/s  1016%    --  -16%  -36%  -50%  -66%  -73% 
  T:L   200/s  1240%   19%    --  -24%  -40%  -60%  -68% 
  A:O   270/s  1663%   57%   31%    --  -21%  -47%  -57% 
  H:O   300/s  2133%  100%   66%   26%    --  -33%  -46% 
  TH:I  500/s  3250%  200%  150%   89%   50%    --  -19% 
  LU:D  610/s  4087%  275%  212%  137%   87%   25%    -- 
 
 Legends:
   A:O: participant=Array::OrdHash
   H:O: participant=Hash::Ordered
   LU:D: participant=List::Unique::DeterministicOrder
   T:I: participant=Tie::IxHash
   T:L: participant=Tie::LLHash
   T:S: participant=Tie::StoredOrderHash
   TH:I: participant=Tie::Hash::Indexed

The above result presented as chart:

#IMAGE: share/images/bencher-result-2.png|/tmp/dxS0EuAWVP/bencher-result-2.png

Result formatted as table (split, part 3 of 3):

 #table3#
 {dataset=>"insert 1000 pairs + return keys 100 times"}
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+
 | participant                      | rate (/s) | time (ms) | pct_faster_vs_slowest | pct_slower_vs_fastest |  errors   | samples |
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+
 | Tie::LLHash                      |         8 |       100 |                 0.00% |              1061.27% |   0.0049  |      20 |
 | Tie::StoredOrderHash             |         8 |       100 |                 3.73% |              1019.53% |   0.0019  |      20 |
 | Array::OrdHash                   |        13 |        79 |                56.42% |               642.39% |   0.00048 |      21 |
 | Tie::IxHash                      |        15 |        69 |                80.98% |               541.64% |   0.00015 |      20 |
 | Tie::Hash::Indexed               |        20 |        50 |               148.79% |               366.76% |   0.00071 |      20 |
 | Hash::Ordered                    |        61 |        16 |               662.88% |                52.22% |   0.00011 |      20 |
 | List::Unique::DeterministicOrder |        94 |        11 |              1061.27% |                 0.00% | 9.1e-05   |      20 |
 +----------------------------------+-----------+-----------+-----------------------+-----------------------+-----------+---------+

The above result formatted in L<Benchmark.pm|Benchmark> style:

        Rate   T:L   T:S   A:O   T:I  TH:I   H:O  LU:D 
  T:L    8/s    --    0%  -20%  -31%  -50%  -84%  -89% 
  T:S    8/s    0%    --  -20%  -31%  -50%  -84%  -89% 
  A:O   13/s   26%   26%    --  -12%  -36%  -79%  -86% 
  T:I   15/s   44%   44%   14%    --  -27%  -76%  -84% 
  TH:I  20/s  100%  100%   58%   37%    --  -68%  -78% 
  H:O   61/s  525%  525%  393%  331%  212%    --  -31% 
  LU:D  94/s  809%  809%  618%  527%  354%   45%    -- 
 
 Legends:
   A:O: participant=Array::OrdHash
   H:O: participant=Hash::Ordered
   LU:D: participant=List::Unique::DeterministicOrder
   T:I: participant=Tie::IxHash
   T:L: participant=Tie::LLHash
   T:S: participant=Tie::StoredOrderHash
   TH:I: participant=Tie::Hash::Indexed

The above result presented as chart:

#IMAGE: share/images/bencher-result-3.png|/tmp/dxS0EuAWVP/bencher-result-3.png


=head2 Sample benchmark #2

Benchmark command (benchmarking module startup overhead):

 % bencher --cpanmodules-module OrderedHash --module-startup

Result formatted as table:

 #table4#
 +----------------------------------+-----------+-------------------+-----------------------+-----------------------+---------+---------+
 | participant                      | time (ms) | mod_overhead_time | pct_faster_vs_slowest | pct_slower_vs_fastest |  errors | samples |
 +----------------------------------+-----------+-------------------+-----------------------+-----------------------+---------+---------+
 | Tie::LLHash                      |        20 |                10 |                 0.00% |               142.41% | 0.00081 |      20 |
 | Hash::Ordered                    |        20 |                10 |                19.61% |               102.67% | 0.00039 |      20 |
 | Tie::Hash::Indexed               |        20 |                10 |                21.42% |                99.65% | 0.00044 |      21 |
 | List::Unique::DeterministicOrder |        19 |                 9 |                25.72% |                92.82% | 0.00017 |      21 |
 | Tie::IxHash                      |        20 |                10 |                36.08% |                78.13% | 0.00023 |      21 |
 | Array::OrdHash                   |        16 |                 6 |                46.94% |                64.97% | 0.0001  |      20 |
 | Tie::StoredOrderHash             |        20 |                10 |                52.27% |                59.20% | 0.00075 |      20 |
 | perl -e1 (baseline)              |        10 |                 0 |               142.41% |                 0.00% | 0.00027 |      20 |
 +----------------------------------+-----------+-------------------+-----------------------+-----------------------+---------+---------+


The above result formatted in L<Benchmark.pm|Benchmark> style:

                          Rate   T:L   H:O  TH:I   T:I   T:S  LU:D   A:O  perl -e1 (baseline) 
  T:L                   50.0/s    --    0%    0%    0%    0%   -5%  -19%                 -50% 
  H:O                   50.0/s    0%    --    0%    0%    0%   -5%  -19%                 -50% 
  TH:I                  50.0/s    0%    0%    --    0%    0%   -5%  -19%                 -50% 
  T:I                   50.0/s    0%    0%    0%    --    0%   -5%  -19%                 -50% 
  T:S                   50.0/s    0%    0%    0%    0%    --   -5%  -19%                 -50% 
  LU:D                  52.6/s    5%    5%    5%    5%    5%    --  -15%                 -47% 
  A:O                   62.5/s   25%   25%   25%   25%   25%   18%    --                 -37% 
  perl -e1 (baseline)  100.0/s  100%  100%  100%  100%  100%   89%   60%                   -- 
 
 Legends:
   A:O: mod_overhead_time=6 participant=Array::OrdHash
   H:O: mod_overhead_time=10 participant=Hash::Ordered
   LU:D: mod_overhead_time=9 participant=List::Unique::DeterministicOrder
   T:I: mod_overhead_time=10 participant=Tie::IxHash
   T:L: mod_overhead_time=10 participant=Tie::LLHash
   T:S: mod_overhead_time=10 participant=Tie::StoredOrderHash
   TH:I: mod_overhead_time=10 participant=Tie::Hash::Indexed
   perl -e1 (baseline): mod_overhead_time=0 participant=perl -e1 (baseline)

The above result presented as chart:

#IMAGE: share/images/bencher-result-4.png|/tmp/dxS0EuAWVP/bencher-result-4.png

To display as an interactive HTML table on a browser, you can add option C<--format html+datatables>.

=head1 FAQ

=head2 What is an Acme::CPANModules::* module?

An Acme::CPANModules::* module, like this module, contains just a list of module
names that share a common characteristics. It is a way to categorize modules and
document CPAN. See L<Acme::CPANModules> for more details.

=head2 What are ways to use this Acme::CPANModules module?

Aside from reading this Acme::CPANModules module's POD documentation, you can
install all the listed modules (entries) using L<cpanm-cpanmodules> script (from
L<App::cpanm::cpanmodules> distribution):

 % cpanm-cpanmodules -n OrderedHash

Alternatively you can use the L<cpanmodules> CLI (from L<App::cpanmodules>
distribution):

    % cpanmodules ls-entries OrderedHash | cpanm -n

or L<Acme::CM::Get>:

    % perl -MAcme::CM::Get=OrderedHash -E'say $_->{module} for @{ $LIST->{entries} }' | cpanm -n

or directly:

    % perl -MAcme::CPANModules::OrderedHash -E'say $_->{module} for @{ $Acme::CPANModules::OrderedHash::LIST->{entries} }' | cpanm -n

This Acme::CPANModules module contains benchmark instructions. You can run a
benchmark for some/all the modules listed in this Acme::CPANModules module using
the L<bencher> CLI (from L<Bencher> distribution):

    % bencher --cpanmodules-module OrderedHash

This Acme::CPANModules module also helps L<lcpan> produce a more meaningful
result for C<lcpan related-mods> command when it comes to finding related
modules for the modules listed in this Acme::CPANModules module.
See L<App::lcpan::Cmd::related_mods> for more details on how "related modules"
are found.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/Acme-CPANModules-OrderedHash>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-Acme-CPANModules-OrderedHash>.

=head1 SEE ALSO

L<Acme::CPANModules::HashUtilities>

L<Acme::CPANModules> - about the Acme::CPANModules namespace

L<cpanmodules> - CLI tool to let you browse/view the lists

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 CONTRIBUTING


To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.

Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:

 % prove -l

If you want to build the distribution (e.g. to try to install it locally on your
system), you can install L<Dist::Zilla>,
L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
that are considered a bug and can be reported to me.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2023 by perlancar <perlancar@cpan.org>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Acme-CPANModules-OrderedHash>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=cut
