Re: Sort hash keys in C...

Rudolf Lippan <[email protected]>
Newsgroups gmane.comp.lang.perl.modules.dbi.sybase.devel
Message-ID <[email protected]>

On Mon, 19 May 2008 10:15:26 +0100, Tim Bunce <[email protected]> wrote:
> 
> On Fri, May 16, 2008 at 01:45:51AM +0100, Rudolf Lippan wrote:
>> On Thu, 15 May 2008 13:21:34 +0100, Tim Bunce <[email protected]>
> wrote:
>> > It would be good to add a clear comment somewhere that the pointers in
>> > the keys array are only valid until perl next free mortal temps.
>> > (There are faster ways of doing that but the use case for the numeric
>> > keys isn't speed critical.)
>> >
>> Suck as, if you don't mind me asking?
> 
> ("Such as", I presume :)
> 

Yes!  Good thing I did not double bounce on the 's' or it could have been
rather confusing.

> Or, more deeply, instead of sorting an array of int's you could sort an
> array of { int key_as_int, char *ptr_to_key } structs. That way you'd
> also avoid the potential risk of problems with keys like "01" or "1.0"
> that change when converted to int and back to a string.
> 

That was my first thought, but I gave up on in when I realised that I 
would have to figure a place to put the typedef 

I changed the code to use a struct (although I just threw the declaration
in ).
It makes it cleaner and gets rid of the sv_2mortal'd copy of the keys.


>> >>
 
> Whatever seems reasonable (and best for ShowErrorStatement usage).
> 
I went and had the wrapper unSVify the params and call _join_hash_sorted().
This
way there is no need to create a bunch of SVs that get thrown away.


-r
concat_hash.patch.patch (text/x-patch, 17.7 KB)
==== Patch <concat_hash.patch> level 1
Source: 2032998a-08c0-11dd-b685-1998e50afd6a:/local/dbi:863
Target: 50811bd7-b8ce-0310-adc1-d9db26280581:/dbi:11280
        (http://svn.perl.org/modules/dbi)
Log:
 r858@pandora:  rlippan | 2008-05-24 11:30:46 -0400
 Make local branch of dbi
 r859@pandora:  rlippan | 2008-05-24 12:29:52 -0400
 before changing hash_sort.
 r860@pandora:  rlippan | 2008-05-24 12:30:28 -0400
 Add test script
 r861@pandora:  rlippan | 2008-05-24 12:49:09 -0400
 make join_hash_sorted not take SV* here ints/char* would do.
 r862@pandora:  rlippan | 2008-05-24 14:35:23 -0400
 use struct when sorting.
 r863@pandora:  rlippan | 2008-05-24 14:35:53 -0400
 remove warnings. turn off benchmark tests

=== trunk/DBI.xs
==================================================================
--- trunk/DBI.xs	(revision 11280)
+++ trunk/DBI.xs	(patch concat_hash.patch level 1)
@@ -209,6 +209,141 @@
     return buf;
 }
 
+typedef struct num_srt_info {
+    char *key;
+    UV numeric;
+} num_srt_info;
+
+static int
+_cmp_number (val1, val2)
+    const void *val1;
+    const void *val2;
+{
+    int first, second;
+
+    first = ((struct num_srt_info *)val1)->numeric;
+    second = ((struct num_srt_info *)val2)->numeric;
+
+    if (first == second)
+        return 0;
+    else if (first > second)
+	return 1;
+    else 
+	return -1;
+}
+
+static int 
+_cmp_str (val1, val2)
+    const void *val1;
+    const void *val2;
+{
+    return strcmp( *(char **)val1, *(char **)val2);
+}
+
+static char **
+_sort_hash_keys (hash, sort_order, total_length)
+    HV *hash;
+    int sort_order;
+    STRLEN *total_length;
+{
+    dTHX;
+    I32 hv_len, key_len;
+    HE *entry;
+    char **keys;
+    unsigned int idx = 0;
+    STRLEN tot_len = 0;
+    bool numberish = 1;
+    struct num_srt_info *numbers;
+
+    hv_len = hv_iterinit(hash);
+    if (!hv_len)
+        return 0;
+
+    Newx(keys, hv_len, char *);
+    Newx(numbers, hv_len, struct num_srt_info);
+
+    while ((entry = hv_iternext(hash))) {
+        *(keys+idx) = hv_iterkey(entry, &key_len);
+        tot_len += key_len;
+        
+        if (grok_number(*(keys+idx), key_len, &(numbers+idx)->numeric) != IS_NUMBER_IN_UV)
+            numberish = 0;
+
+        (numbers+idx)->key = *(keys+idx);
+        ++idx;
+    }
+
+    if (0 != total_length)
+        *total_length = tot_len;
+
+    if (sort_order <0)
+        sort_order = numberish ? 1 : 0;
+
+    if (0 == sort_order || 0 == numberish ) {
+        qsort(keys, hv_len, sizeof(char*), _cmp_str);
+    } else {
+        qsort(numbers, hv_len, sizeof(struct num_srt_info), _cmp_number);
+        for (idx = 0; idx < hv_len; ++idx)
+            *(keys+idx) = (numbers+idx)->key;
+/* SvPV_nolen(sv_2mortal(newSViv(numbers[idx])));  */
+    }
+
+    Safefree(numbers);
+    return keys;
+}
+
+
+static SV *
+_join_hash_sorted (hash, kv_sep, pair_sep, not_neat, sort)
+    HV *hash;
+    char *kv_sep;
+    char *pair_sep;
+    int not_neat;
+    int sort;
+{
+	dTHX;
+        I32 hv_len;
+        STRLEN kv_sep_len, pair_sep_len, hv_val_len, total_len = 0;
+        char **keys;
+        char *value_string;
+        unsigned int i = 0;
+        SV **hash_svp;
+        SV *return_sv;
+
+        kv_sep_len = strlen(kv_sep);
+        pair_sep_len = strlen(pair_sep);
+
+        keys = _sort_hash_keys(hash, sort, &total_len);
+        if (!keys)
+            return newSVpv("", 0);
+
+        hv_len = hv_iterinit(hash);
+        /* total_len += Separators + quotes + term null */
+        total_len += kv_sep_len*hv_len + pair_sep_len*hv_len+2*hv_len+1;
+	return_sv = newSV(total_len);
+        sv_setpv(return_sv, ""); /* quell undef warnings */
+
+        for (i=0; i<hv_len; ++i) {
+            hash_svp = hv_fetch(hash, keys[i], strlen(keys[i]), 0);
+            if (!hash_svp) {
+                 warn("No Hash entry with key: %s", keys[i]);
+                continue;
+            }
+
+            value_string = (not_neat) ? 
+                (SvOK(*hash_svp) ? SvPV(*hash_svp, hv_val_len) : "") : 
+                neatsvpv(*hash_svp,0);
+            sv_catpvf(return_sv, (not_neat) ? "%s%s'%s'%s" : "%s%s%s%s",
+                keys[i], kv_sep, value_string, (i<hv_len-1) ? pair_sep : "");
+        }
+
+        Safefree(keys);
+
+        return return_sv;
+}
+
+
+
 /* handy for embedding into condition expression for debugging */
 /*
 static int warn1(char *s) { warn(s); return 1; }
@@ -4236,6 +4371,43 @@
     RETVAL
 
 
+SV *
+_concat_hash_sorted (hash, kv_sep_sv, pair_sep_sv, value_format_sv,sort_type_sv)
+    HV *hash
+    SV *kv_sep_sv
+    SV *pair_sep_sv
+    SV *value_format_sv
+    SV *sort_type_sv
+
+    PREINIT:
+
+    STRLEN kv_sep_len, pair_sep_len;
+    char *kv_sep, *pair_sep;
+    int not_neat, sort;
+
+    CODE:
+
+        kv_sep = SvPV(kv_sep_sv, kv_sep_len);
+        pair_sep = SvPV(pair_sep_sv, pair_sep_len);
+
+        if (SvGMAGICAL(value_format_sv))
+            mg_get(value_format_sv);
+        not_neat = SvTRUE(value_format_sv);
+
+
+        sort = (SvOK(sort_type_sv)) ? SvIV(sort_type_sv) : -1;
+
+
+        RETVAL = _join_hash_sorted(hash,kv_sep, pair_sep, not_neat, sort);
+
+    OUTPUT:
+        RETVAL
+
+
+
+
+
+
 MODULE = DBI   PACKAGE = DBI::var
 
 void
=== trunk/t/ConcatHash.t
==================================================================
--- trunk/t/ConcatHash.t	(revision 11280)
+++ trunk/t/ConcatHash.t	(patch concat_hash.patch level 1)
@@ -0,0 +1,191 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl CatHash.t'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use Test::More tests => 36;
+BEGIN { use_ok('DBI') };
+no warnings 'uninitialized';
+
+# null and undefs -- segfaults?;
+is (DBI::_concat_hash_sorted({ }, "=", ":", 0, undef), "");
+eval {DBI::_concat_hash_sorted(undef, "=", ":", 0, undef), undef};
+like ($@ || "", qr/hash is not a hash reference/); #XXX check this
+is (DBI::_concat_hash_sorted({ }, undef, ":", 0, undef), "");
+
+is (DBI::_concat_hash_sorted({ }, "=", undef, 0, undef), "");
+is (DBI::_concat_hash_sorted({ }, "=", ":", undef, undef),"");
+
+# Simple segfault tests?
+ok(DBI::_concat_hash_sorted({bob=>'two', fred=>'one' }, "="x12000, ":", 1, undef));
+ok(DBI::_concat_hash_sorted({bob=>'two', fred=>'one' }, "=", ":"x12000, 1, undef));
+ok(DBI::_concat_hash_sorted({map {$_=>undef} (1..1000)}, "="x12000, ":", 1, undef));
+ok(DBI::_concat_hash_sorted({map {$_=>undef} (1..10000)}, "=", ":"x12000, 1, undef), 'test');
+ok(DBI::_concat_hash_sorted({map {$_=>undef} (1..100)}, "="x12000, ":"x12000, 1, undef), 'test');
+
+my $simple_hash = {
+    bob=>"there",
+    jack=>12,
+     fred=>"there",
+     norman=>"there",
+    # sam =>undef
+};
+
+my $simple_numeric = {
+    1=>"there",
+    2=>"there",
+    3=>"there",
+    32=>"there",
+    16 => 'yo',
+    07 => "buddy",
+    49 => undef,
+};
+
+my $simple_mixed = {
+    bob=>"there",
+    jack=>12,
+     fred=>"there",
+     norman=>"there",
+     sam =>undef,
+    1=>"there",
+    2=>"there",
+    3=>"there",
+    32=>"there",
+    16 => 'yo',
+    07 => "buddy",
+	    49 => undef,
+};
+
+my $simple_float = {
+    1.12 =>"there",
+    3.1415926 =>"there",
+    2.718281828 =>"there",
+    32=>"there",
+    1.6 => 'yo',
+    0.78 => "buddy",
+    49 => undef,
+};
+
+#eval {
+#    DBI::_concat_hash_sorted($simple_hash, "=",,":",1,12);
+#};
+ok(1," Unknown sort order");
+#like ($@, qr/Unknown sort order/, "Unknown sort order");
+
+
+
+## Loopify and Add Neat
+
+
+my %neats = (
+    "Neat"=>0, 
+    "Not Neat"=> 1
+);
+my %sort_types = (
+    guess=>undef, 
+    numeric => 1, 
+    lexical=> 0
+);
+my %hashes = (
+    Numeric=>$simple_numeric, 
+    "Simple Hash" => $simple_hash, 
+    "Mixed Hash" => $simple_mixed,
+    "Float Hash" => $simple_float
+);
+
+for $sort_type (keys %sort_types){
+    for $neat (keys %neats) {
+        for $hash(keys %hashes) {
+            test_concat_hash($hash, $neat, $sort_type);
+        }
+    }
+}
+
+sub test_concat_hash {
+    my ($hash, $neat, $sort_type) = @_;
+    is (
+        #DBI::_concat_hash_sorted(
+        _concat_hash_sorted(
+            $hashes{$hash}, "=", ":",$neats{$neat}, $sort_types{$sort_type}
+        ),
+        _concat_hash_sorted(
+            $hashes{$hash} , "=", ":",$neats{$neat}, $sort_types{$sort_type}
+        ),
+        "$hash - $neat $sort_type"
+    );
+}
+
+if (0) {
+    eval {
+        use Benchmark qw(:all);
+        cmpthese(200_000, {
+	    Perl => sub {_concat_hash_sorted($simple_hash, "=", ":",0,undef); },
+	    C=> sub {DBI::_concat_hash_sorted($simple_hash, "=", ":",0,1);}
+        });
+
+        print "\n";
+        cmpthese(200_000, {
+  	    NotNeat => sub {DBI::_concat_hash_sorted(
+                $simple_hash, "=", ":",1,undef);
+            },
+	    Neat    => sub {DBI::_concat_hash_sorted(
+                $simple_hash, "=", ":",0,undef);
+            }
+        });
+    };
+
+}
+#CatHash::_concat_hash_values({ }, ":-",,"::",1,1);
+
+
+sub _concat_hash_sorted {
+    my ( $hash_ref, $kv_separator, $pair_separator, $value_format, $sort_type ) = @_;
+    # $value_format: false=use neat(), true=dumb quotes
+    # $sort_type: 0=lexical, 1=numeric, undef=try to guess
+
+    $keys = _get_sorted_hash_keys($hash_ref, $sort_type);
+    my $string = '';
+    for my $key (@$keys) {
+        $string .= $pair_separator if length $string > 0;
+        my $value = $hash_ref->{$key};
+        if ($value_format) {
+            $value = (defined $value) ? "'$value'" : 'undef';
+        }
+        else {
+            $value = DBI::neat($value,0);
+        }
+        $string .= $key . $kv_separator . $value;
+    }
+    return $string;
+}
+
+use Scalar::Util qw(looks_like_number);
+sub _get_sorted_hash_keys {
+    my ($hash_ref, $sort_type) = @_;
+    my $sort_guess = 1;
+    if (not defined $sort_type) {
+        #my $first_key = (each %$hash_ref)[0];
+        #$sort_type = looks_like_number($first_key);
+
+        $sort_guess =  
+            (1!=looks_like_number($_)) ? 0:$sort_guess for keys %$hash_ref;
+        $sort_type = $sort_guess unless (defined $sort_type);
+    }
+    
+    my @keys = keys %$hash_ref;
+    no warnings 'numeric';
+    return [ ($sort_type && $sort_guess)
+        ? sort {$a <=> $b} @keys
+        : sort    @keys
+    ];
+}
+
+
+
+#########################
+
+# Insert your test code below, the Test::More module is use()ed here so read
+# its man page ( perldoc Test::More ) for help writing this test script.
+

==== BEGIN SVK PATCH BLOCK ====
Version: svk v2.0.2 (linux)

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
==== END SVK PATCH BLOCK ====
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.