#!/usr/bin/c++script // Demonstration of views::zip and views::concat progdesc("Demonstration of views::zip and views::concat"); // 3 vector we will use in the following examples. // Notice that tb1 has 4 items while tb2 and tbs have 5. vector tb1={10,20,30,40}; vector tb2={100,200,300,400,500}; vector tbs={"string1","string2","string3","string4","string5"}; // You would like to process the two vectors in the same loop // You can't use the ramge for syntax, because you want to iterate both vectors at once // Back to plain old for(;;) syntax cout << "Old style for(;;) loop\n"; for (unsigned i=0; i(item),get<1>(item)); } // views::zip packs the two range items into a tuple. A tuple is like a struct // but without names. The get syntax has to be used. // Lets rewrite the previous example using the C++ unpack feature. cout << "Using views::zip and unpack\n"; for (auto &&[item1,item2]:views::zip(tb1,tb2)){ cout << format ("\titem1={} item2={}\n",item1,item2); } // views::zip can zip together different item types cout << "views::zip with tb1,tb2 and tbs\n"; for (auto &&[item1,item2,item3]:views::zip(tb1,tb2,tbs)){ cout << format ("\titem1={} item2={} item3={}\n",item1,item2,item3); } // This is still safe, but some items are not displayed because tb1.size() < tb2.size() // views::zip accepts ranges. Not just vectors. Really ? // in this example, we use views::iota() to generate a filler value because tb1.size() < tb2.size() cout << "views::zip with concat(tb1,views::iota(1000)),tb2 and tbs\n"; for (auto &&[item1,item2,item3]:views::zip(views::concat(tb1,views::iota(1000)),tb2,tbs)){ cout << format ("\titem1={} item2={} item3={}\n",item1,item2,item3); } // Another example using views::repeat(), which repeats endlessly the same value cout << "views::zip with concat(tb1,views::repeat(-1)),tb2 and tbs\n"; for (auto &&[item1,item2,item3]:views::zip(views::concat(tb1,views::repeat(-1)),tb2,tbs)){ cout << format ("\titem1={} item2={} item3={}\n",item1,item2,item3); } // This works, but only because we know that tb1.size() < tb2.size() // Is there a solution working for both cases. // Yes, but it becomes tedious to write. So we create a fill() function. // We need a views::take() at the end to limit the output since views::repeat // creates infinite ranges. auto fill = [](auto &tb, auto value){ return views::concat(tb,views::repeat(value)); }; cout << "views::zip with concat(tb1,views::repeat(-1)),concat(tb2,views::repeat(-1) and tbs\n"; for (auto &&[item1,item2,item3]:views::zip(fill(tb1,-1),fill(tb2,-1),fill(tbs,"")) | views::take(max(tb1.size(),tb2.size()))){ cout << format ("\titem1={} item2={} item3={}\n",item1,item2,item3); } // Sometime, you want to do some transformation, filtering, and then apply zip to the result. // It could be handy to do // vector | transform | filter | zip(tb2,tbs) | ... // But zip is not an adaptor. // Here is an annoying example. You want to print some lines, but the first is different. It goes like this // title first line // second line // third line // Here is one way to do this. We do not want to get back to the old for (;;) loop, because it may cause problems. cout << "Avoiding the old for(;;) to format the first line.\n"; bool first = true; for (auto &num:tb1){ if (first){ cout << format ("title {}\n",num); first = false; }else{ cout << format (" {}\n",num); } } // A little annoying. Here is another solution with views::zip() cout << "Same thing with views::zip(views::iota(1),tb1). We avoid a variable.\n"; for (auto &&[lineno,num]:views::zip(views::iota(1),tb1)){ if (lineno == 1){ cout << format ("title {}\n",num); }else{ cout << format (" {}\n",num); } } // Here is another solution with views::enumerate. It works like the solution above cout << "Same thing with tb1|views::enumerate. enumerate starts at 0.\n"; for (auto &&[lineno,num]:tb1 | views::enumerate){ if (lineno == 0){ cout << format ("title {}\n",num); }else{ cout << format (" {}\n",num); } } // A little simpler, but well. Can we get rid of the if else, using views::concat cout << "Same thing with views::zip and views::concat\n"; for (auto &&[title,num]:views::zip(views::concat(views::single("title"),views::repeat(" ")),tb1)){ cout << format ("{} {}\n",title,num); } // Clearly, less lines. A little simpler. The first element of the views::zip is used for formating. // Just to make it clear, put it on a different line cout << "A little easier to read\n"; auto column1 = views::concat(views::single("title"),views::repeat(" ")); for (auto &&[title,num]:views::zip(column1,tb1)){ cout << format ("{} {}\n",title,num); } // This problem is common. What if the title is variable. Counting spaces is not fun. cout << "Playing smart with the column1 title\n"; auto format_column1 = [](string title){ return views::concat(views::single(title),views::repeat(string(title.size(),' '))); }; for (auto &&[title,num]:views::zip(format_column1("This is the title"),tb1)){ cout << format ("{} {}\n",title,num); }