Asked  7 Months ago    Answers:  5   Viewed   25 times

I have a multidimensional array that stores people.

Array (
   id93294 => (array (
             Name => "Tom Anderson",
             Birthday => "03/17/1975"),
   id29349 => (array (
             Name => "Tom Anderson",
             Birthday => "03/17/1975")
)

Kind of like that except with more info for the people, so I want to first sort by birthdays THEN sort by another attribute (if their hometown matches their current location) but once I do the second sort on the array it loses the first sort I did using the birthdays...

How do I sort multiple times without it messing up my previous sorts.

P.S. I am using uasort.

 Answers

43

Update

I recently answered this question in a much more capable manner in the "definitive" topic on sorting multidimensional arrays. You can safely skip reading the rest of this answer and directly follow the link for a much more capable solution.

Original answer

The function uasort lets you define your own comparison function. Simply put all the criteria you want inside that.

For example, to sort by birthday and then by name:

function comparer($first, $second) {
    // First see if birthdays differ
    if ($first['birthday'] < $second['birthday']) {
        return -1;
    }
    else if ($first['birthday'] > $second['birthday']) {
        return 1;
    }

    // OK, birthdays are equal. What else?
    if ($first['name'] < $second['name']) {
        return -1;
    }
    else if ($first['name'] > $second['name']) {
        return 1;
    }

    // No more sort criteria. The two elements are equal.
    return 0;
}

I am ignoring the fact that in your example, the birthdays are not in a format that can be ordered by a simple comparison using the operator <. In practice you would convert them to a trivially-comparable format first.

Update: if you think that maintaining a bunch of these multiple-criteria comparers could get ugly real fast, you find me in agreement. But this problem can be solved as any other in computer science: just add another level of abstraction.

I 'll be assuming PHP 5.3 for the next example, in order to use the convenient anon function syntax. But in principle, you could do the same with create_function.

function make_comparer() {
    $criteriaNames = func_get_args();
    $comparer = function($first, $second) use ($criteriaNames) {
        // Do we have anything to compare?
        while(!empty($criteriaNames)) {
            // What will we compare now?
            $criterion = array_shift($criteriaNames);

            // Do the actual comparison
            if ($first[$criterion] < $second[$criterion]) {
                return -1;
            }
            else if ($first[$criterion] > $second[$criterion]) {
                return 1;
            }

        }

        // Nothing more to compare with, so $first == $second
        return 0;
    };

    return $comparer;
}

You could then do:

uasort($myArray, make_comparer('birthday', 'name'));

This example possibly tries to be too clever; in general I don't like to use functions that do not accept their arguments by name. But in this case, the usage scenario is a very strong argument for being too clever.

Wednesday, March 31, 2021
 
viper
answered 7 Months ago
42
function set_val(array &$arr, $path,$val)
{
   $loc = &$arr;
   foreach(explode('.', $path) as $step)
   {
     $loc = &$loc[$step];
   }
   return $loc = $val;
}
Wednesday, March 31, 2021
 
laurent
answered 7 Months ago
39

When you set cell values individually, you have the option of setting the datatype explicitly, but when you use the fromArray() method, you don't have this option.

However, by default, PHP uses a default value binder to identify datatypes from the values passed, and set the cell datatype accordingly. This default behaviour is defined in a class /PHPExcel/Cell/DefaultValueBinder.php.

So you can create your own value binder, as described in the PHPExcel Documentation, that would set every value as a string datatype.

Something like:

class PHPExcel_Cell_MyColumnValueBinder extends PHPExcel_Cell_DefaultValueBinder implements PHPExcel_Cell_IValueBinder
{
    protected $stringColumns = [];

    public function __construct(array $stringColumnList = []) {
        // Accept a list of columns that will always be set as strings
        $this->stringColumns = $stringColumnList;
    }

    public function bindValue(PHPExcel_Cell $cell, $value = null)
    {
        // If the cell is one of our columns to set as a string...
        if (in_array($cell->getColumn(), $this->stringColumns)) {
            // ... then we cast it to a string and explicitly set it as a string
            $cell->setValueExplicit((string) $value, PHPExcel_Cell_DataType::TYPE_STRING);
            return true;
        }
        // Otherwise, use the default behaviour
        return parent::bindValue($cell, $value);
    }
}

// Instantiate our custom binder, with a list of columns, and tell PHPExcel to use it
PHPExcel_Cell::setValueBinder(new PHPExcel_Cell_MyColumnValueBinder(['A', 'B', 'C', 'E', 'F']));

$objPHPExcel = new PHPExcel();
$objPHPExcel->getActiveSheet()->fromArray($dataArray,null,"A2");
Friday, May 28, 2021
 
Wilk
answered 5 Months ago
26

First, replace line breaks with <br />:

$post = nl2br($post_data['content']);

Then replace double <br /> with closing and opening paragraph tag (the original line break is maintained by nl2br, so I use a regular expression, that matches all styles of line breaks):

$post = '<p>' . preg_replace('#(<br />[rn]+){2}#', '</p><p>', $post) . '</p>';

Note that this is XHTML syntax, if you want to have HTML, change the code as follows:

$post = nl2br($post_data['content'], false);
$post = '<p>' . preg_replace('#(<br>[rn]+){2}#', '</p><p>', $post) . '</p>';

Test:

$post_data['content'] = <<<TXT
line 1 paragraph 1,
line 2 paragraph 1.

line 3 paragraph 2,
line 4 paragraph 2,
line 5 paragraph 2.
TXT;

$post = nl2br($post_data['content'], false);
$post = '<p>' . preg_replace('#(<br>[rn]+){2}#', "</p>nn<p>", $post) . '</p>';

echo $post;

Test Output:

<p>line 1 paragraph 1,<br>
line 2 paragraph 1.</p>

<p>line 3 paragraph 2,<br>
line 4 paragraph 2,<br>
line 5 paragraph 2.</p>
Saturday, May 29, 2021
 
Manmay
answered 5 Months ago
38

Well I managed to figure it out. Here's the gist for anyone in the future:

Best to use Named Ranges. Basically define before hand the list items (named ranges) in cells. this can be on a different worksheet. So, lets use an example of countries and cities. dropdown 1 will have counties, dropdown 2 will have cities. So define the named ranges. One will be called countries. the other two named ranges will be called after the name of the countries. so, lets go.

$objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("A1", "UK");
    $objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("A2", "USA");

    $objPHPExcel->addNamedRange( 
        new PHPExcel_NamedRange(
            'countries', 
            $objPHPExcel->getSheetByName('Worksheet 1'), 
            'A1:A2'
        ) 
    );

$objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("B1", "London");
    $objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("B2", "Birmingham");
    $objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("B3", "Leeds");
    $objPHPExcel->addNamedRange( 
        new PHPExcel_NamedRange(
            'UK', 
            $objPHPExcel->getSheetByName('Worksheet 1'), 
            'B1:B3'
        ) 
    );

$objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("C1", "Atlanta");
    $objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("C2", "New York");
    $objPHPExcel->getSheetByName('Worksheet 1')->SetCellValue("C3", "Los Angeles");
    $objPHPExcel->addNamedRange( 
        new PHPExcel_NamedRange(
            'USA', 
            $objPHPExcel->getSheetByName('Worksheet 1'), 
            'C1:C3'
        ) 
    );

So thats the named ranges. One is the country, the others are the ranges for the cities for each of the countries. Now to load up the first dropdown to select the countries.

$objValidation = $objPHPExcel->getActiveSheet()->getCell('A1')->getDataValidation();
    $objValidation->setType( PHPExcel_Cell_DataValidation::TYPE_LIST );
    $objValidation->setErrorStyle( PHPExcel_Cell_DataValidation::STYLE_INFORMATION );
    $objValidation->setAllowBlank(false);
    $objValidation->setShowInputMessage(true);
    $objValidation->setShowErrorMessage(true);
    $objValidation->setShowDropDown(true);
    $objValidation->setErrorTitle('Input error');
    $objValidation->setError('Value is not in list.');
    $objValidation->setPromptTitle('Pick from list');
    $objValidation->setPrompt('Please pick a value from the drop-down list.');
    $objValidation->setFormula1("=countries"); //note this!

Now for the dropdown to load the cities depending on the country. This uses an excel function called Indirect. Basically returns the selected value. hence the similarly named ranges. so i select "UK", it loads the named range called "UK" which has the UK cities.

$objValidation = $objPHPExcel->getActiveSheet()->getCell('B1')->getDataValidation();
    $objValidation->setType( PHPExcel_Cell_DataValidation::TYPE_LIST );
    $objValidation->setErrorStyle( PHPExcel_Cell_DataValidation::STYLE_INFORMATION );
    $objValidation->setAllowBlank(false);
    $objValidation->setShowInputMessage(true);
    $objValidation->setShowErrorMessage(true);
    $objValidation->setShowDropDown(true);
    $objValidation->setErrorTitle('Input error');
    $objValidation->setError('Value is not in list.');
    $objValidation->setPromptTitle('Pick from list');
    $objValidation->setPrompt('Please pick a value from the drop-down list.');
    $objValidation->setFormula1('=INDIRECT($A$1)'); 

Notes: I have used two sheets. Worksheet 1 to hold the data and sheet 0 or default to hold the dropdowns. All the best.

Monday, August 2, 2021
 
aries12
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :