Special Random Collection

Hello

I would like to make a random content bewteen 1 and n (max project entries)
But my request is quite special, let me explain, here is my foreach loop :

<?php foreach($site->find('projects')->children()->visible() as $project): ?>
    <section>
        <article>
            <?php echo $project->title()->html() ?>
        </article>
    </section>
 <?php endforeach ?>

I obtain :

<section>
    <article>
        Project 1
    </article>
</section>
<section>
    <article>
        Project 2
    </article>
</section>
<section>
    <article>
        Project 3
    </article>
</section>


What I’m looking for is something like

<section>
    <article>
        Project 1
    </article>
</section>
<section>
    <article>
        Project 3
    </article>
    <article>
        Project 2
    </article>
</section>

Or even :

<section>
    <article>
        Project 3
    </article>
    <article>
        Project 1
    </article>
    <article>
        Project 2
    </article>
</section>

This means that the random function I want to have could decide how many article or section to choose and displayed it according to the total number of the project pages.
Do you have an idea ?

I created this custom pages method to create chunks of random size. Save it in a plugin file, e.g. site/plugins/methods.php.

  /**
   * Creates chunks of random size
   *
   * @param int $min Minimum number of items in chunk
   * @param int $max Maximum number of items in chunk
   * @return object A new collection with an item for each chunk and a subcollection in each chunk
   * Usage:
   * $projects = page('projects')->children()->shuffle();
   * $max = mt_rand(1,$projects->count());
   * $min = mt_rand(1, $max);
   * $chunks = $projects->randomChunk($min,$max);
   */
pages::$methods['randomChunk'] = function($pages, $min, $max) {
  $temp = clone $pages;
  $chunks = [];
  $size=1;
  ($min >=1 && $min <= $max)? $min=$min:$min=1;
  $max = $max <= $pages->count()? $max: $pages->count();
  while ($temp->count() > 0) {
    $size = mt_rand($min,$max);
    array_push($chunks, array_splice($temp->data,0, $size));
  }
  $chunkCollections = [];
  foreach($chunks as $items) {
    $collection = clone $pages;
    $collection->data = $items;
    $chunkCollections[] = $collection;
  }
  // convert the array of chunks to a collection object
  return new Collection($chunkCollections);
};

Use in your template

<?php
$projects = page('projects')->children()->visible()->shuffle();

/**
 **create random $min and $max values;
 ** these set the range for the random $size variable within the randomChunk() method; 
 ** instead of random values, you can also set one or both parameters to a fixed value, 
 ** depending on what sorts of results you want to get
*/
$max = mt_rand(1,$projects->count());
$min = mt_rand(1, $max);

$chunks = $projects->randomChunk($min,$max);
// $chunks is a nested collection, so we need two foreach loops to get the chunks and then all items in each chunk

foreach($chunks as $items): ?>
  <section>
    <?php foreach($items as $project): ?>
    <article class="">
      <?= $project->title()?>
    </article>
    <?php endforeach ?>
  </section>
  <hr>
<?php endforeach ?>
1 Like

WOAW Fantastic !!!
I felt that my question was too complicated, or not logical … but this function works incredibly well!
Thanks also for the comments that explain very well.
I hope it can be used by other people.

Bye, have a nice sunday !

:tada::tada::tada::tada::tada::tada::tada:

Come to think of it, the same can be done with mapping and grouping:

$projects = page('projects')->children()->visible();
$max = mt_rand(1,$projects->count());
$min = 1; // can be random as well

$callback = function($p) {
  return $p->random;
};
$chunks = $projects->shuffle()->map(function($p) use($min,$max){
    $p->random = mt_rand($min,$max);
    return $p;
})->group($callback);


foreach($chunks as $section): ?>
  <section>
    <?php  foreach($section as $value): ?>
    <article class="">
      <?= $value->title()?>
    </article>
    <?php endforeach ?>
  </section>
<hr>
<?php endforeach ?>

Don’t know what is more performant.

Edit: Looks like the first version is faster than the second method, at least in my tests with 100 and something pages.

Hi,
I could use the first method you send me, and it’s working perfeclty. After some use, I realized I also have to make another condition : I would like to have a minimum of 2 articles inside each sections.

I tried a lot a things, but I don’t really know how to write this. Could you help me with that ?

Forget everything I said in the deleted post, I didn’t think.

In fact, I had an error in the first method, which is now corrected, so that you can use it with $min set to 2 and you should be fine.

1 Like

Really perfect, thank you so much.
Hope to share you as soon as possible my project.

It’s not really perfect, it needs another modification because the last section can still have only one item

  <?php

  pages::$methods['randomChunk'] = function($pages, $min, $max) {
  $temp = clone $pages;
  $chunks = [];

  ($min >=1 && $min <= $max)? $min=$min:$min=1;
  $max = $max <= $pages->count()? $max: $pages->count();
  while ($temp->count() > 0) {
    $count = $temp->count();
    $size = mt_rand($min,$max);
    if($count - $size >= $min) {
      array_push($chunks, array_splice($temp->data,0, $size));
    } else {
      array_push($chunks, array_splice($temp->data,0, $count));
    }

  }
  $chunkCollections = [];
  foreach($chunks as $items) {
    $collection = clone $pages;
    $collection->data = $items;
    $chunkCollections[] = $collection;
  }
  // convert the array of chunks to a collection object
  return new Collection($chunkCollections);
};

How many accounts do you use?

I didn’t saw the last article in your previous method could generate only one item…For now, I can’t say your second method is better : I have to continue to refresh the page to confirm that there are at least two articles. But I completely trust you !
And sorry for the confusion of accounts, I use one for personal projects, and the other for the professional : it’s easier to find topics.

:heart_eyes:

It helps to test with only a few projects, then you see this immediately. What the last version does is check if the difference between the random number and the current item count is equal to or greater than $min(edited), and if not, it adds all current items to the last chunk.

Hi Sonja
You already helped me a lot with your custom chunk methods to get random section and article with a minimum of 2 articles by sections.

My client asks me for something simple that I can’t do : always display the credits to the first position ! So my goal now is to display the first project from page('projects')->children()->visible() into the first « article » even if the page is refreshed.

How could I do an exception inside this foreach ?

What do you mean by credits?

Ow, no matter, I would say this first page contains important information that I must display first despite of the random function

What I’d do:

  • add a shuffle parameter to the method
  • get the first item of the cloned collection (limit(1))
  • define the rest as the cloned collection minus the first
  • shuffle or not depending on parameter
  • push the first item into the first chunk

I am not a PHP professional and I could never put that in place, but I feel (like the last time) that it looks more complicated than it seems.
I was hoping to exclude the page with ->not() like

$projects = page('film')->children()->not('project-b')->visible()->shuffle();

and added this page into the first position of the collection

Collection Object
(
    [0] => Children Object
        (
            [0] => film/project-b // Here my first project
            [1] => film/project-d
        )

    [1] => Children Object
        (
            [0] => film/project-e
            [1] => film/project-f
        )

)

I didn’t found a function to include a value in the first object of the collection, could be a solution?

If you don’t want to do it in the function, why don’t you just print the first page first thing in your loop with a few if conditions?

Good question, I can’t tell you yet the true difference, but I think if the first page comes first, it doesn’t matter for me because it already exists a minimum of two articles into the first section.
I would love to translate your idea but I’m afraid to don’t get it, could you provide me a simple example of this on a simple layout like this ?

<?php foreach($chunks as $section): ?>
  <section>
    <?php  foreach($section as $value): ?>
    <article>
      <?= $value->title()?>
    </article>
    <?php endforeach ?>
  </section>
<?php endforeach ?>