Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

Δημοσ.

Καλησπέρα παιδιά!

Λοιπόν, έχουμε έναν php array στην $navigation. Σας δίνω την full μορφή του, εδώ (είναι από var_dump).

Είναι της μηχανής vBulletin, οπότε δεν έχει νόημα να αλλάξω τη μορφή/δομή του.

 

Ουσιαστικά αυτός είναι ο array που γίνονται build τα στοιχεία της πλευρικής στήλης στο admin control panel.

 

Ο $navigation με την αρχική του μορφή που σας παρέθεσα, όταν γίνεται build η navigation bar, εμφανίζονται με την ακόλουθη σειρά τα blocks με τα στοιχεία τους:

Settings

vBulletin Blog

vBulletin CMS

Panjo Marketplace

Advertisting

Styles & Templates

Languages & Phrases

κτλ κτλ

 

Αυτό που θέλω είναι να κάνω sort τον πίνακα με χρήση άλλου πίνακα με στοιχεία.

 

Έτσι, αν έχουμε:

$order = array('Scheduled Tasks', 'Smilies', 'Usergroups', 'FAQ');

Πως μπορώ να κάνω sort τον πίνακα ώστε - χωρίς να αλλοιωθεί η υπόλοιπη δομή του - να πάνε στην κορυφή τα:

Scheduled Tasks

Smilies

Usergroups

FAQ

... να ακολουθήσουν τα υπόλοιπα στοιχεία:

Settings

vBulletin Blog

vBulletin CMS

Panjo Marketplace

Advertisting

Styles & Templates

Languages & Phrases

 

Έχω βρει διάφορα παραδείγματα στο stackoverflow, αλλά για πιο απλές μορφές πινάκων.

 

Ευχαριστώ προκαταβολικά για την όποια βοήθεια. :-)

 

 

Δημοσ.

Καταρχήν ο πίνακας που δίνεις είναι sorted όχι μόνο με τη σειρά εμφάνισης των ηθοποιών αλλά και με τα integer keys. Τι ακριβώς από τα δύο είναι που καθορίζει τη σειρά εμφάνισης στο vbulletin? Αν δεν ξέρεις τι πρέπει να φτιάξεις δε θα καταφέρεις να το φτιάξεις...

 

Αν ας πούμε κάνεις 

$v = end($navigation);
array_pop($navigation);
$navigation[1] = $v;

έρχεται το τελευταίο στοιχείο πρώτο; Αν ναι, τότε μετράνε τα keys. Αν όχι, μετράει η σειρά εμφάνισης.

Δημοσ.

Βασικά ο κώδικας του vBulletin κάνει ένα ksort($navigation); πριν προχωρήσει στη foreach που κάνει το build, επομένως τα integer keys καθορίζουν τη σειρά.

 

Έβαλα μετά την ksort τον κώδικά σου, αλλά το τελευταίο στοιχείο δεν έγινε πρώτο.

 

Οπότε βάσει του $order στο αρχικό μου μήνυμα, θέλω να γίνει sort ο $navigation ώστε να πάνε στην κορυφή με τη σχετική σειρά αυτά που έχω ορίσει, και τα υπόλοιπα να ακολουθούν από κάτω βάσει των integer keys. Right;

 

Thanks :-)

Δημοσ.

Βασικά ο κώδικας του vBulletin κάνει ένα ksort($navigation); πριν προχωρήσει στη foreach που κάνει το build, επομένως τα integer keys καθορίζουν τη σειρά.

 

Όχι, επομένως το εσωτερικό array order καθορίζει τη σειρά και απλά υπάρχει μια ksort για να εξισώσει το order αυτό με τα integer keys.  :)

 

Άρα μπορείς να γλυτώσεις κόπο κάνοντας τη βρωμοδουλειά σου μετά την ksort (αλλά θα είναι brittle) ή απλά θα αντικαταστήσεις την ksort με κάτι δικό σου. Ή ακόμα καλύτερα, πώς προκύπτουν αυτά τα integer keys? Δε μπορείς να πας στην πηγή κατευθείαν;

 

Anyway, δώσε ένα var_export αντί για var_dump γιατί βαρ να περνάω τον πίνακα που δίνεις σε παράδειγμα με το χέρι.  :)

Δημοσ.

Εδώ και το var_export. :-)

 

Νομίζω τα integer keys προκύπτουν από τα xml αρχεία (cpnav_vbulletin.xml κτλ) που χρησιμοποιούνται για να γίνει build η navigation bar, αλλά δεν θέλω να τα τροποποιήσω ένα ένα.

 

Ναι αμέ, μπορώ να βάλω τη λύση μετά την ksort. Απλά δεν ξέρω πως να κάνω το sort βάσει του $order πίνακα..

Δημοσ.

Αγαπημένη PHP πόσο εύκολα κάνεις μερικά πράγματα... not.

 

Λοιπόν ο κώδικας είναι

function rearrangeTopLevel($input) {
    $weights = array_merge(
        ['Scheduled Tasks', 'Smilies', 'Usergroups', 'FAQ'],
        array_map('key', $input)
    ); // 1

    $weights = array_flip(array_keys(array_flip($weights))); // 2

    usort(
        $input,
        function($x, $y) use ($weights) {
            return $weights[key($x)] <=> $weights[key($y)];
        }
    );


    return $input;
}

PHP 7+ επειδή χρησιμοποιώ spaceship operator για να το παίξω κουλ. Στην πραγματικότητα ο κώδικας είναι γραμμένος έτσι που μπορείς να αντικαταστήσεις το <=> με απλό μείον και θα πάρεις ακριβώς το ίδιο αποτέλεσμα σε οποιαδήποτε PHP >= 5.3.

 

What's going on...

 

Καταρχήν υπάρχουν μερικές αναφορές στη συνάρτηση key(). Αυτό όχι επειδή έχουμε να κάνουμε με πίνακες και μα μου αλλά επειδή στη συγκεκριμένη περίπτωση τυχαίνει βάσει της δομής που δίνεις η τιμή από την οποία θα αποφασίσουμε πού πρέπει να πάει κάθε γραμμή του αρχικού πίνακα να είναι το πρώτο και μοναδικό key του υποπίνακα που αποτελεί τη γραμμή. Δηλαδή αφού έχεις κάτι του στυλ

 

[

    ["foo" => ...],

    ["bar" => ...],

]

 

και θέλεις να ταξινομήσεις με κάποια λογική πάνω στις τιμές "foo" και "bar", προφανώς θα χρειαστεί key() ή αντίστοιχο σε κάθε γραμμή για να πάρεις κατ' αρχήν στα χέρια σου την τιμή που χρειάζεσαι. Θα μπορούσα λοιπόν αντί για key να γράφω getComparisonValue ή κάτι παρόμοιο και

 

function getComparisonValue($item) { return key($item); }

 

μόνο και μόνο για να ξεκαθαρίσω ότι απλά τυχαίνει το comparison value να δίνεται από την key() στη συγκεκριμένη περίπτωση. Αυτή η σκέψη μέχρι εδώ μπορεί να γίνει και χωρίς να έχεις ιδέα πώς θα ταξινομήσεις απο κει και πέρα.

 

Τώρα, για τις custom sorting functions πολλές φορές (τις περισσότερες?) αυτό που συμβαίνει είναι ότι έχεις κάπου έναν πίνακα-αναφορά $weights ("τα μεγαλύτερα νούμερα είναι πιο βαριά και πάνε πιο κάτω") και μέσα από μια usort ή αντίστοιχη κάνεις ακριβώς αυτό που κάνω στο τέλος:

  • Από τα δύο στοιχεία προς σύγκριση $x, $y πάρε το "comparison value" που λέω παραπάνω
  • Μετέτρεψέ το στο "βάρος" που θες (πολύ συχνά κάνοντας $weights[$comparisonValue] όπως κάνω εδώ αλλά όχι 100% απαραίτητα)
  • Επέστρεψε την integer τιμή που πρέπει για το sort (αν τα weights είναι integers αυτό μπορεί να γίνει με απλή αφαίρεση, αλλιώς spaceship operator, αλλιώς γράφεις λίγο παραπάνω κώδικα για τη σύγκριση)

Μπορείς να κάνεις print_r($weights) πριν τη usort για να δεις ακριβώς τι έχει μέσα η μεταβλητή αν θες και κάτι πιο χειροπιαστό.

 

Οπότε τώρα μένουν οι γραμμές // 1 και // 2 προς εξήγηση.

 

Στην 1 αυτό που κάνω είναι απλό, παίρνω όλες τις "comparison value" που θα χρησιμοποιηθούν αργότερα και τις βάζω στη σειρά μέσα σε ένα πίνακα. Πρώτα αυτές που θες με τη σειρά που θες και στη συνέχεια όλες τις υπόλοιπες με τη σειρά που εμφανίζονται, αφού αυτό είναι που ζητάς στο αποτέλεσμα.

 

Το αποτέλεσμα μέχρι εκεί είναι της μορφής

 

[

    0 => 'Scheduled Tasks',

    1 => 'Smilies',

    ...

    35 => 'Scheduled Tasks',

    ...

]

 

Αυτό είναι διαφορετικό με δύο τρόπους από το αποτέλεσμα που θα ήθελα να έχω για να μπορώ να κάνω $weights[$comparisonValue]:

  1. Keys είναι τα weights και values τα comparison values, ενώ εγώ τα θέλω ανάποδα (ή εναλλακτικά θα μπορούσα να χρησιμοποιήσω array_search() μέσα στην usort για να πάω από value σε key αντί για [] για να πάω από key σε value, αλλά άστο αυτό μη τα μπερδεύουμε κι άλλο).
  2. Μερικά values εμφανίζονται δύο φορές, μία που τα έβαλα εγώ με το ζόρι και μία που υπήρχαν εξαρχής στα δεδομένα εισόδου.

Αυτά τα 2 "διορθώνονται" στη γραμμή // 2. Το πρώτο (εσωτερικό) array_flip() κάνει σχεδόν όλα όσα θέλω: και swap keys <=> values, και αφαίρεση των διπλοεγγραφών (διατηρεί και τη σειρά εμφανίσεων των τιμών, πράγμα που δε μου χρειάζεται στο τελικό αποτέλεσμα αλλά μου χρειάζεται στο επόμενο ενδιάμεσο βήμα).

 

Το μόνο πρόβλημα που παραμένει τώρα είναι ότι τα $weights είναι "λάθος", μοιάζουν κάπως έτσι:

 

[
    'Scheduled Tasks' => 35,
    'Smilies' => 32,
    'Usergroups' => 20,
    'FAQ' => 10,
    'Settings' => 4,
    ....
]
 
Σ' αυτό το σημείο οι τιμές 35, 32, 20, 10, whatever είναι παντελώς αδιάφορες και θα θέλαμε να τις αντικαταστήσουμε με οποιαδήποτε αύξουσα ακολουθία integers (δε χρειάζεται καν να είναι συνεχόμενοι). Αυτό μπορεί να γίνει με αρκετούς τρόπους αλλά οι περισσότεροι και πιο βολικοί έχουν να κάνουν με array_keys(), το οποίο θα μας κάνει ακριβώς αυτό που θέλουμε, συν ένα ακόμα πράγμα που δε θέλουμε: ξανά swap keys and values. Ε, πατάς κι ένα ακόμα array_flip() από πάνω για να κάνεις "undo" αυτή την ανεπιθύμητη παρενέργεια και έφτασες εκεί που ήθελες. usort και κύριος.
 
Enjoy!
Δημοσ.

defacer, καταρχήν σε ευχαριστώ για την όλη ανάλυση! :-)
Δυστυχώς τα βιβλία που αφορούν την PHP δεν κάνουν τόσο ειδικευμένες αναλύσεις και γενικά έχω δυσκολία με φορτωμένα προβλήματα που αφορούν πίνακες...
 
Λοιπόν το τέσταρα και φαίνεται ότι δουλεύει όπως πρέπει. Είδα και με var_dump την $weights πριν το usort και κατάλαβα τι προσπαθεί να κάνει το script και η usort. Στον "καθαρό" vBulletin board μου στο localhost λειτουργεί κανονικά.
Δοκίμασα όμως να το τρέξω και σε μια εκδοχή του board με extra addons (κι άρα με extra navigation items), και κάτι παίζει με ορισμένα στοιχεία.
 
Για παράδειγμα, με:

['FAQ', 'Styles & Templates', 'Scheduled Tasks', 'Smilies', 'Usergroups']

και πηγαίο var_export της $navigation, αυτό.
... παίρνω τα ακόλουθα στοιχεία ως πρώτα, και για κάποιο λόγο το FAQ εμφανίζεται πολύ πιο κάτω. Το παθαίνει και με άλλα στοιχεία, αν τα βάλω μέσα σε αυτά που θέλω να δώσω βάρος*:
Styles & Templates
Scheduled Tasks
Smilies
Usergroups
[......]
FAQ
 
*πχ αν βάλεις στη λίστα για βάρος, το Quick Add Moderators κτλ, δε θα είναι στη κορυφή αλλά σε πιο κάτω θέση.

 

Η $weights φαίνεται να είναι εντάξει (αφού πχ δίνει integer 0 => FAQ, integer 1 => Styles & Templates κτλ κτλ), άρα ίσως κάτι παίζει με την usort ή γίνεται κάποιο conflict κατά το sorting; :fear:

Δημοσ.

Βασικά το θέμα είναι ότι δεν έχουμε ξεκάθαρη δομή πίνακα οπότε το αποτέλεσμα προκύπτει από αυτά που παρατήρησα με το μάτι στα γρήγορα. Θα έπρεπε καλύτερα πρώτα απ' όλα μόνος σου να έχεις βάλει σε λέξεις τη δομή του πίνακα και να μπορείς να δημιουργήσεις ένα φανταστικό παράδειγμα -- προφανώς, αν δε μπορείς να ορίσεις το πρόβλημα ούτε να το λύσεις μπορείς.

 

Απ' ότι βλέπω στο δεύτερο παράδειγμα υπάρχει η περίπτωση να έχεις φορτωμένα πολλά top-level items στο ίδιο "weight" στα δεδομένα εισόδου. Όμως η χρήση της key() στη γραμμή // 1 σημαίνει ότι θα λάβουμε υπόψη μόνο το πρώτο από αυτά όταν υπολογίζουμε weights, και σύμφωνα με αυτό θα ταξινομηθούν πακέτο και όλα τα υπόλοιπα. Το FAQ είναι πακέτο με κάτι άλλα και έρχεται τρίτο, οπότε θα πάει όπου πάει και το πρώτο στοιχείο του πακέτου ("Game System").

 

Για τη δεύτερη προσπάθεια λοιπόν φαίνεται ότι πρέπει να σπάσεις πρώτα όλα αυτά τα πακέτα διαφορετικά δε μπορείς να μετακινήσεις τα περιεχόμενά τους ανεξάρτητα το ένα από το άλλο.

 

Ένας τρόπος που θα δουλέψει αλλά δε θα το έκανα σε production code γιατί είναι πολύ ακαταλαβίστικος αλλά βαριέμαι οπότε there you have it, είναι μέσα στη function με το καλημέρα σας να κάνεις αυτό

$input = array_merge(...array_map(function($r) { return array_chunk($r, 1, true); }, $input));

και μετά τα υπόλοιπα ως έχουν.

  • Like 1
Δημοσ.

Περισσότερες σειρές κώδικα αλλά πιστεύω πως κάνει αυτό που ψάχνεις πιο αναλυτικά:

function FindIndex($list, $name) {
	// vriskoume to index apo to $list pou antistoixei sto $name
	for ($i = 0; $i < count($list); $i++) {
		if ($list[$i]["name"] == $name) {
			return $list[$i]["index"];
		}
	}
}

function SortArray($array, $order) {
	$map = array();
	// dimiourgoume mia kainourgia lista apo to $navigations gia na doulepsoume xwris na peira3oume to structure tou prototupou
	$a = 0;
	foreach ($array as $key => $value) { 
		$list[$a]["index"] = $key;
		$list[$a]["name"] = key($array[$key]);
		array_push($map, $key);
		$a += 1;
	}
	// vriskoume kai topo8etoume ta elements tou $order sthn arxh tou telikou array ($output)
	for ($i = 0; $i < count($order); $i++) {
		$n = FindIndex($list, $order[$i]);
		if (isset($n)) {
			$output[$list[$i]["index"]] = array(key($array[$n]) => reset($array[$n]));
		}
	}
	// psaxnoume gia ta upoloipa elements apo to $list pou den anoikoun sto $order kai ta pros8etoume sto $output me thn seira tous
	$a = count($order);
	for ($i = 0; $i < count($list); $i++) {
		if (!in_array($list[$i]["name"], $order)) {
			$n = FindIndex($list, $list[$i]["name"]);
			if (isset($n)) {
				$output[$map[$a]] = array(key($array[$n]) => reset($array[$n]));
			}
			$a += 1;
		}
	}
	return $output;
}

$sorted = SortArray($navigation, ['Scheduled Tasks', 'Smilies', 'Usergroups', 'FAQ']);

Online αποτέλεσμα: http://sandbox.onlinephpfunctions.com/code/60a817a2ef71376a012d74678dd5d8fa92166a93

  • Like 1
Δημοσ.

 

Μερικά σχόλια στα γρήγορα εκεί που έπεσε το μάτι

 

Αντί για

 

array(key($array[$n]) => reset($array[$n]))

 

καλύτερα

 

array_slice($array[$n], 0, 1, true);

 

Αντί για

 

$list[$a]["index"] = $key;

$list[$a]["name"] = key($array[$key]);

 

καλύτερα

 

$list[] = ["index" => $key, "name" => key($array[$key])]; // και δε χρειάζεσαι το $a

 

Αντί για

 

array_push($map, $key);

 

πάντα

 

$map[] = $key;

 

(και μετά από όλα αυτά προκύπτει ότι το loop εκεί μπορεί να γίνει ένα απλό array_map)

 

 

Γενικά υπάρχουν πολλά πράγματα που θα μπορούσαν να γίνουν καλύτερα ακόμα κι αν θέλεις επίτηδες να το κάνεις πιο αναλυτικό.

  • Like 2
Δημοσ.

Αντί για

 

$list[$a]["index"] = $key;

$list[$a]["name"] = key($array[$key]);

 

καλύτερα

 

$list[] = ["index" => $key, "name" => key($array[$key])]; // και δε χρειάζεσαι το $a

 

Τα σπάει, δεν είχα ιδέα για τέτοια σύνταξη. :D

Δημοσ.

Thanks παιδιά!
Όλα φαίνονται να λειτουργούν ok. :-)
 
Μια τελευταία ερώτηση για εγκυκλοπαιδικούς κυρίως λόγους (έκανα διάφορες προσπάθειες με την call_user_func_array αλλά έμπλεξα τα μπούτια μου).
Το:

$input = array_merge(...array_map(function($r) { return array_chunk($r, 1, true); }, $input));

Πως μεταφράζεται σε PHP < 5.6;
Αναφέρομαι στον splat operator. :-)

Δημοσ.

Πως μεταφράζεται σε PHP < 5.6;

Αναφέρομαι στον splat operator. :-)

 

Το πιο κοντινό σε splat operator είναι χρήση της call_user_func_array έτσι:

$input = array_merge(...array_map(function($r) { return array_chunk($r, 1, true); },$input));

$input = call_user_func_array('array_merge', array_map(...));

Αλλά καλύτερα κάντο "manually" έτσι:

 

$demuxed = [];
foreach ($input as $row) {
    foreach ($row as $key => $item) {
        $demuxed[] = [$key => $item];
    }
}

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...