www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Java streams Vs LINQ Vs D

reply "bearophile" <bearophileHUGS lycos.com> writes:
Linked on Reddit I've seen a nice comparison of Java streams Vs 
dotnet LINQ:

http://blog.informatech.cr/2013/03/24/java-streams-preview-vs-net-linq/

Despite they aren't a complete list, those little challenges are 
well chosen, they are operations done commonly. So I have 
translatated them to D with Phobos. For most of them I have found 
a nice D translation. But few of them uncover holes in Phobos, 
that I alredy know. (Maybe some of them are not really Phobos 
holes, but just my lack of knowledge about Phobos and D. So your 
better solutions are welcome).

If you want to read the whole list of my translations:
http://codepad.org/0KtXu7nh

Below I list just the five troubled challenges, with the LINQ 
solution followed by one or more D solutions.

For all the solutions I import several modules:

import std.stdio, std.algorithm, std.range, std.typecons, 
std.traits,
        std.array, std.string;

- - - - - - - - - - - -

Challenge 2: Indexed Filtering

Find all the names in the array "names" where the length of the 
name is less than or equal to the index of the element + 1.

string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 
1).ToList();


In D:

     auto names2 = ["Sam","Pamela", "Dave", "Pascal", "Erik"];
     auto nameRange = iota(size_t.max)
                      .zip(names2)
                      .filter!q{ a[1].length <= a[0] }
                      .map!q{ a[1] };
     nameRange.writeln;


On Bugzilla I have proposed to add an enumerate():
http://d.puremagic.com/issues/show_bug.cgi?id=5550

With it the D code improves:


     auto nameRange2 = names2
                       .enumerate
                       .filter!q{ a[1].length <= a[0] }
                       .map!q{ a[1] };
     nameRange2.writeln;


If D gains a syntax to unpack tuples in function signatures the 
code becomes (untested):


     auto nameRange2 = names2
                       .enumerate
                       .filter!((i, n) => n.length <= i)
                       .map!q{ a[1] };
     nameRange2.writeln;


Beside adding enumerate() that is useful in many other 
situations, another (not alternative!) idea is to add 
iFilter/iMap (that mean indexed filter and indexed map), where 
the filtering or mapping function is supplied by an index+item 
2-tuple:

     auto nameRange2 = names2.iFilter!((i, a) => a.length <= i);

Or equivalently:

     auto nameRange2 = names2.iFilter!q{ a.length <= i };


Those ifilter/imap functions are present in the standard library 
of the F# language.

- - - - - - - - - - - -

Challenge 3: Selecting/Mapping

Say we have a list of names and we would like to print “Hello” in 
front of all the names:

List<string> nameList1 = new List(){ "Anders", "David", "James",
                                      "Jeff", "Joe", "Erik" };
nameList1.Select(c => "Hello! " + c).ToList()
          .ForEach(c => Console.WriteLine(c));


In Phobos there is no forEach(), so you have to use foreach:

     auto nameList1 = ["Anders", "David", "James", "Jeff", "Joe", 
"Erik"];
     foreach (name; nameList1)
         writeln("Hello! ", name);


The only advantage I see of a forEach() over foreach() is that 
it's usable at the end of an UFCS chain.

- - - - - - - - - - - -

Challenge 12: Grouping by a Criterium

Group the elements of a collection of strings by their length.

string[] names = {"Sam", "Samuel", "Samu", "Ravi", "Ratna",  
"Barsha"};
var groups = names.GroupBy(c => c.Length);


In Phobos there is a group() but it can't be used here because it 
returns just one of the equivalent grouped items. And I can't use 
std.array.assocArray for similar reasons.


     auto names3 = ["Sam", "Samuel", "Samu", "Ravi", "Ratna", 
"Barsha"];
     string[][size_t] groups;
     foreach (name; names3)
         groups[name.length] ~= name;
     groups.byValue.writeln;


Andrei has recently written a groupBy, not yet merged:
https://github.com/D-Programming-Language/phobos/pull/1186


Using that future groupBy the D code improves a little (untested. 
In DMD 2.063 schwartzSort accepts a string literal too):

     auto names3 = ["Sam", "Samuel", "Samu", "Ravi", "Ratna", 
"Barsha"];
     auto groups = names3
                   .schwartzSort!q{ a.length }
                   .groupBy!q{ a.length == b.length };
     groups.writeln;


By the way, I like Python for having a free len() function that's 
usable for higher order functions like map and filter. In Phobos 
there is walkLength():

     auto names3 = ["Sam", "Samuel", "Samu", "Ravi", "Ratna", 
"Barsha"];
     auto groups = names3
                   .schwartzSort!walkLength
                   .groupBy!q{ a.walkLength == b.walkLength };
     groups.writeln;


Unlike schwartzSort the Phobos group/groupBy use a comparison 
function like "a.length == b.length" instead of a less flexible 
but more handy single function like "c => c.Length". So I'd like 
something like a keyGroup/keyGroupBy that accepts a 
single-argument function as schwartzSort. (And I'd like 
schwartzSort to be renamed "keySort").

     auto names3 = ["Sam", "Samuel", "Samu", "Ravi", "Ratna", 
"Barsha"];
     auto groups = names3
                   .schwartzSort!walkLength
                   .keyGroupBy!walkLength;
     groups.writeln;



Another problem with group/groupBy is that they work by sorting. 
But a hash-based O(n) group/groupBy is also conceivable, 
potentially faster, and leading to simpler code, because you 
don't need to sort the items first:

     auto names3 = ["Sam", "Samuel", "Samu", "Ravi", "Ratna", 
"Barsha"];
     auto groups = names3.hashKeyGroupBy!walkLength;
     groups.writeln;


Uhm. The name "hashKeyGroupBy" is becoming a bit too much complex 
:-) So maybe it's better not go there.

- - - - - - - - - - - -

Challenge 13: Filter Distinct Elements

Obtain all the distinct elements from a collection.

string[] songIds = {"Song#1", "Song#2", "Song#2", "Song#2", 
"Song#3", "Song#1"};
var uniqueSongIds = songIds.Distinct();


This is not too much bad in D, there is uniq(), but first you 
need to .sort or .idup.sort or .array.sort the original 
array/range:


     auto songIds = ["Song#1", "Song#2", "Song#2", "Song#2", 
"Song#3", "Song#1"];
     auto uniqueSongIds = songIds.sort().uniq;
     uniqueSongIds.writeln;


A hash-based uniq that doesn't need a previous sorting is 
conceivable. But see also below.

- - - - - - - - - - - -

Challenge 14: Union of Two Sets

Join together two sets of items.
LINQ

List<string> friends1 = new List<string>() {"Anders", 
"David","James",
                                             "Jeff", "Joe", 
"Erik"};
List<string> friends2 = new List<string>() { "Erik", "David", 
"Derik" };
var allMyFriends = friends1.Union(friends2);


This seems a bit too much complex to do in D+Phobos:


     auto friends1 = ["Anders", "David","James", "Jeff", "Joe", 
"Erik"];
     auto friends2 = ["Erik", "David", "Derik"];
     auto allMyFriends = 
friends1.sort().setUnion(friends2.sort()).uniq;
     allMyFriends.writeln;


Note that you have to call uniq at the end because that's not a 
set union, it's a badly named function. A better name for it is 
"bagUnion" because it doesn't remove the duplications, and a set 
operation should.

For the Challenge 13 and 14 I suggest to not add more functions 
to std.algorithm, and instead just rely on a set data structure, 
as in Python:


 song_ids = ["Song#1", "Song#2", "Song#2", "Song#2", "Song#3", 
 "Song#1"]
 set(song_ids)



 friends1 = ["Anders", "David","James", "Jeff", "Joe", "Erik"]
 friends2 = ["Erik", "David", "Derik"]
 set(friends1).union(friends2)



In my D1 dlibs I had a Set!T data structure (with a set() helper function) that offered a similar syntax (here I use D2 UFCS): auto songIds = ["Song#1", "Song#2", "Song#2", "Song#2", "Song#3", "Song#1"]; auto uniqueSongIds = songIds.set; auto friends1 = ["Anders", "David","James", "Jeff", "Joe", "Erik"]; auto friends2 = ["Erik", "David", "Derik"]; auto allMyFriends = friends1.set.united(friends2); - - - - - - - - - - - - Bye, bearophile
Mar 27 2013
next sibling parent "Jesse Phillips" <Jessekphillips+D gmail.com> writes:
On Wednesday, 27 March 2013 at 22:19:01 UTC, bearophile wrote:
 Challenge 3: Selecting/Mapping

 Say we have a list of names and we would like to print “Hello” 
 in front of all the names:

 List<string> nameList1 = new List(){ "Anders", "David", "James",
                                      "Jeff", "Joe", "Erik" };
 nameList1.Select(c => "Hello! " + c).ToList()
          .ForEach(c => Console.WriteLine(c));


 In Phobos there is no forEach(), so you have to use foreach:

     auto nameList1 = ["Anders", "David", "James", "Jeff", 
 "Joe", "Erik"];
     foreach (name; nameList1)
         writeln("Hello! ", name);


 The only advantage I see of a forEach() over foreach() is that 
 it's usable at the end of an UFCS chain.

Hmm, I would have thought this should work: auto nameList1 = ["Anders", "David", "James", "Jeff", "Joe", "Erik"]; nameList1.copy( a => writeln("Hello! ", a) ); According std.range: "r(e); R is e.g. a delegate accepting an E."
Mar 27 2013
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 27 March 2013 at 22:19:01 UTC, bearophile wrote:
 In Phobos there is no forEach(), so you have to use foreach:

     auto nameList1 = ["Anders", "David", "James", "Jeff", 
 "Joe", "Erik"];
     foreach (name; nameList1)
         writeln("Hello! ", name);


 The only advantage I see of a forEach() over foreach() is that 
 it's usable at the end of an UFCS chain.

import std.algorithm : chain, map, copy; import std.stdio; void main() { ["Anders", "David", "James", "Jeff", "Joe", "Erik"] .map!(str => chain("Hello! ", str, "\n")) .copy(stdout.lockingTextWriter()); } Will do it as well.
Mar 27 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Chris Cain:

 import std.algorithm : chain, map, copy;
 import std.stdio;

 void main() {
     ["Anders", "David", "James", "Jeff", "Joe", "Erik"]
         .map!(str => chain("Hello! ", str, "\n"))
         .copy(stdout.lockingTextWriter());
 }


 Will do it as well.

Right. forEach() has the advantage of being an unversal "sink". Bye, bearophile
Mar 27 2013
prev sibling parent "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 28 March 2013 at 00:08:31 UTC, Jesse Phillips wrote:
 Hmm, I would have thought this should work:

     auto nameList1 = ["Anders", "David", "James", "Jeff", "Joe",
 "Erik"];
 	 nameList1.copy( a => writeln("Hello! ", a) );

 According std.range: "r(e); 	R is e.g. a delegate accepting an 
 E."

Filed as bug. http://d.puremagic.com/issues/show_bug.cgi?id=9823 Though I'd need to specify a type nameList1.copy( (string a) => writeln("Hello! ", a) );
Mar 27 2013