I am building a badges app in CodeIgniter using a MySQL database. There are 3 types of badges: level 1, level 2 and level 3 badges.
Each level 2 badge is awarded once you get all the required level 1 badges and each level 3 badge is awarded once you get all the required level 1 and level 2 badges. There is also a level 3 badge that requires getting 4 level 3 badges.
I have the following database table for my badges relations:
badge_id - children_id
1 - 20
1 - 25
20 - 40
20 - 45
26 - 40
25 - 39
40 - 50
I need a function that returns me all the ancestors of a badge.
For example, if the function recieved the argument 50 it would return: 40, 26, 20, 1. Any ideas?
If you want to handle it by pure mysql, you need to declare a mysql stored procedure which has some depth limitations.
Instead of that I would recommend using a simple recursive PHP function to return what you need:
function get_ancestors($child_id, $badges)
{
$found = array();
foreach($badges as $k => $row){
if ($row['children_id'] == $child_id){
$found = array_merge($found, get_ancestors($row['badge_id'], $badges));
$found[] = $row['badge_id'];
}
}
return $found;
}
The $badges variable holds all the rows from your badges relations table, just simply read it out from the database.
Based on the example you gave, for the following call
get_ancestors(50, $badges);
the output would be:
Array
(
[0] => 1
[1] => 20
[2] => 26
[3] => 40
)
Related
I'm looking for a formula (PHP) that I can use to assign a score base on rarity of a number inside a collection. Serial#1 being the rarest.
For example, I have few sets of collections.
Collection 1 - Total number of items = 10 (Serial #1 to Serial #10)
Collection 2 - Total number of items = 100 (Serial #1 to Serial #100)
Collection 3 - Total number of items = 3500 (Serial #1 to Serial #3500)
Based on the 3 example sets. Collection 1 is considered the rarest collection because of only 10 items available in the set.
In addition to this. Each item is assigned with its serials, #1 being the best (rarest)
Here is the table of how I visualize the scoring system.
Collection 1
| Serial#| Score |
|:------:| :-----:|
| 1 | 1000 |
| 2 | 900 |
| 3 | 800 |
| 4 | 700 |
| 5 | 600 |
| 6 | 500 |
| 7 | 400 |
| 8 | 300 |
| 9 | 200 |
| 10 | 100 |
Collection 2
| Serial#| Score |
|:------:| :----:|
| 1 | 800 |
| 2 | 700 |
| 3 | 600 |
| 4 | 500 |
| ... | ... |
| 99 | 12 |
| 100 | 10 |
I just made up those scores just for representation.
With the tables above, Serial #1 in Collection 1 has a higher score compared to Serial #1 in Collection 2 because of the rarity of Collection 1.
I have few collections ranging from 1 of a kind (rarest of them all),10, 20, 150, 350, 1000, 5000, 10000, 50000” What “score” is this item supposed to get then?
for the rarest 1 of 1 the score will be based on the score of the
other Serial #1. If for example Collection with 10 items the serial#
get 1000 points then the 1 of 1 i can give 5000 points (this one no
need to calculate)
2. Are all the scores inside a collection supposed to add up to the same value?
No. It doesn't need to add up to the same value as the other collections
Your “made up” scores aren’t really helpful in explaining what kind of scoring logic you want to apply here in the first place
In the example table (made up scores). I just want to show that the different serial number scores. The higher serial number will have a lower score compare to the lower serial#. But the scores will differ from the other collections with the same serial number. Thus I categorized them by collections. Lower item count collections are considered rarer than those with higher item count.
4. But Serial #1 is supposed to have a higher score, than Serial #2, inside the collection? If so, then what would that be based on? Just the ascending order of those #1, #2, etc.?
Maybe it will be based on the ascending order.
5. All 10 items in collection 1 could get the same score?
No.
I don't have any preference on the score the serial number will get. I mean Serial #1 can get X score as long as it will be relative to the rarity of the collection. Serial #1 score will not be the same across collection unless they belong to the same collection rarity.
If I understand you correctly you want to count occurrences of score (value) and sort that occurrence count in a way that the lowest number (serial) will represent the values (score) that are the rarest and the higher the serial number is the value is more common.
For the:
Input Colleciton:
Array
(
[0] => 5
[1] => 4
[2] => 3
[3] => 2
[4] => 1
[5] => 10
[6] => 10
[7] => 11
[8] => 11
[9] => 12
[10] => 12
[11] => 13
[12] => 13
[13] => 14
[14] => 14
[15] => 100
[16] => 100
[17] => 100
[18] => 101
[19] => 101
[20] => 101
[21] => 102
[22] => 102
[23] => 102
[24] => 103
[25] => 103
[26] => 103
[27] => 104
[28] => 104
[29] => 104
)
this code:
<?php
header('Content-Type: text/plain');
// generate colleciton
$collection = [];
# most unique values
$start = 5;
$end = 0;
for ($i = $start; $i > $end; $i--) {
$collection[] = $i;
}
# less unique values (2x the same values)
$start = 10;
$end = 15;
for ($i = $start; $i < $end; $i++) {
$collection[] = $i;
$collection[] = $i;
}
# least unique values
$start = 100;
$end = 105;
for ($i = $start; $i < $end; $i++) {
$collection[] = $i;
$collection[] = $i;
$collection[] = $i;
}
echo "Input Colleciton:\n";
print_r($collection);
# array of [value => how_many_occurences, ... => ...]
$valueCount = array_count_values($collection);
echo "Value count:\n";
print_r($valueCount);
$uniqueBins = [];
# convert to unique bins
foreach ($valueCount as $value => $key) {
$uniqueBins[$key][] = $value;
}
echo "Unique bins:\n";
print_r($uniqueBins);
// optionally sort by value
foreach ($uniqueBins as $key => $bin) {
asort($bin);
$uniqueBins[$key] = $bin;
}
echo "Unique bins after sort:\n";
print_r($uniqueBins);
generates the output:
Unique bins after sort:
Array
(
[1] => Array
(
[4] => 1
[3] => 2
[2] => 3
[1] => 4
[0] => 5
)
[2] => Array
(
[0] => 10
[1] => 11
[2] => 12
[3] => 13
[4] => 14
)
[3] => Array
(
[0] => 100
[1] => 101
[2] => 102
[3] => 103
[4] => 104
)
)
Where values from the array under the key 1 are rarest and values from the array under the key 3 are most common.
The key number is not starting from zero but from 1 because values that are rarest have only single (1) occurrence in the whole collection.
If you want the output bin array to have keys starting from zero and continues in numbering (0,1,2,...) then use array_values(), this way:
$uniqueBins = array_values($uniqueBins);
at the end. By doing that you loose the information about the count (rarity) of values however you still have arrays of values in the order of their rarity (starting from most to the least)
To make this code work you need to have all values into single array $collection but if you have your values in multiple arrays then you can merge all of them into single $collection by using array_merge
Here are headlines to get you started....
Create a DB
Like this
Category :Rarest
uniqueid item name score
hashed#125 Dragon 1000000
hashed#122 Hydra 800000
hashed#100 Medusa 750000
Category :Rarer
uniqueid item name score
hashed other1 50000
hashed other2 30000
hashed other3 10000
Category :Rare
uniqueid item name score
hashed other1 5000
hashed other2 2000
hashed other3 800
This is how you can approach this...
0.hidden readonly checkbox[contains uniquehash] associate with clickable images[showing name of item]
1.[Connect to DB] mysql here
2.[query from tables (all uniqueids 1,2,3 and...)]
3.[from Post get user inputs as array][say user click image showing medusa and hydra - the hidden checkboxes send hashed#100 and hashed#122]
4.[foreach DB values as #strings]( unique ids are broken to individual strings)
5.[foreach user array as #strings]( uniqueids from userinputs are broken to individual strings)
6.[if user string match DB #strings](match each hash]
7.[return query [score] and [name of item] from where #string is found ](only matching ids return their score in this case score 800000 and 750000 as well as Hydra + Medusa)
8.[then [score for match1]] + [score for match2] + match 3 and so on]
(800000+750000)
echo [total score for user selections] + [names of items]
(1.55mil,hydra + medusa ) you can even show they are from the rarest collection
Since the beginning of the process the user in the frontend does not know what the item score is and there is no way to know they just get the total
I am trying to work out how to correct display the rank of points to show a leaderboard.
I currently have the following in my Modal
public function getPointrankAttribute(){
$year = Carbon::parse(Carbon::now())->year;
$pointrank = Points::query()
->select('member_id')->selectRaw('SUM(value) as TotalPoints')
->where('Year','=', $year)
->groupBy('member_id')
->orderByDesc('Totalpoints')
->get();
return $pointrank->search(function($points){
return $points->member_id == $this->id;
}) + 1;
}
This works well, however it is using the row position in the table to show the rank, so the first record is 1st and the 2nd record shows as 2nd.
However the issue arises when members have the same total points, because under this the rank is based on position on in the table
See the following example
|member_id|totalpoints|
| 12 | 20 |
| 3 | 10 |
| 10 | 10 |
| 5 | 5 |
In this example based on my current solution
Member 12 = 1st, Member 3 = 2nd, Member 10 = 3rd, Member 5 = 4th
What I want is the following
Member 12 = 1st, Member 3 = 2nd, Member 10 = 2nd, Member 5 = 4th
It would be awesome if I could also add "=" as well when we have more than 1 member sharing that position (as in Member 3 and 10 in this case)
Thanks in advance
To get rank for your users you will need to assign rank to distinct total points first, using laravel collection helper you can create separate ranks collection irrespective of member ids like
$collection =collect(
[
[
"member_id" => 12,
"totalpoints" => 20
],
[
"member_id" => 3,
"totalpoints" => 10
],
[
"member_id" => 10,
"totalpoints" => 10
],
[
"member_id" => 5,
"totalpoints" => 5
],
]
);
$ranks = $collection->unique('totalpoints')
->values()
->mapWithKeys(function ($item, $index) {
return [$item['totalpoints'] => $index + 1];
});
Make sure the original collection should be ordered based on total points.
The above will create an array of distinct points as keys and values as ranks based on total points like
Array
(
[20] => 1
[10] => 2
[5] => 3
)
Now you can search your members based on id from original collection and get their respective rank from ranks array like
$id = 10;
$ranks[$collection->firstWhere("member_id", $id)["totalpoints"]];
There is another way to get ranks for your records directly from database if the vendor database supports window functions like RANK()
What I want is the following :Member 12 = 1st, Member 3 = 2nd, Member 10 = 2nd, Member 5 = 4th
To achieve your desired results you just need to remove ->values() from above code so that your results are not re indexed
$ranks = $collection->unique('totalpoints')
->mapWithKeys(function ($item, $index) {
return [$item['totalpoints'] => $index + 1];
});
Array
(
[20] => 1
[10] => 2
[5] => 4
)
DEMO
My data in table t1 as below (only 2 record),
+-----------+-----------------+----------+
| shid | lvlmin | lvlmax |
+-----------+-----------------+----------+
| 1 | 1 | 10 |
| 2 | 5 | 10 |
+----------------------------------------+
My php code is:
$userinfo[0] = '9';
$ghunt = DB::fetch_all("SELECT shid FROM t1
WHERE lvlmin <= ".$userinfo[0]." AND lvlmax >= ".$userinfo[0].
"ORDER BY rand() LIMIT 5");
print_r($ghunt);
Result got 2 array:
Array ( [0] => Array ( [shid] => 2 ) [1] => Array ( [shid] => 1 ) )
How do I do when the array result is less than the LIMIT 5 in mysql query, auto use the array result in $ghunt to fill up the array?
What I mean is:
Array (
[0] => Array ( [shid] => 2 )
[1] => Array ( [shid] => 1 )
[2] => Array ( [shid] => 2 )
[3] => Array ( [shid] => 1 )
[4] => Array ( [shid] => 1 )
)
The shid can be random place in array.
Why don't you do something like this?
If (count($ghunt) < 5){
$realResultCount = count($ghunt);
for ($i = realResultCount; $i <= 5; $i++){
$ghunt[$i] = $ghunt[rand(0,realResultCount-1)];
}
}
Basically, what above code does is, if ghunt has less than 5 records in it, it tops it up to 5, by randomly selecting records out of initially returned records.
I don't code PHP, but I can describe one way you can achieve your goal simply. Most languages have a MOD operator, usually % - the php manual page for mod is here
It gives us the remainder of a division operation, so 10 mod 3 is 1, because 10 divided by 3 is 9 remainder 1
A useful property of MOD then, is that it always cycles between 0 and 1 less than what you're modding by. If you mod an incrementing number by 5, the result will always be 0,1,2,3,4,0,1,2,3,4 in a cycle. This means you can have a for loop with some incrementing number, mod by an array length and the result will be an integer that is certainly an array index. If the loop variable goes higher than the end of the array, the mod operator will make it wrap round to the start of the array again
MyArray[ 1746262848 mod MyArray.length ]
Will certainly not crash, even if the array only has 2 items
So for your case, just have a loop.. make he following pseudo code into PHP
// run the loop 5 times
For I as integer = 0 to 4 do
Print MyArray[ i mod MyArray.length ]
If you have 2 items in your array, A and B, it will simply print ABABA
If you have 3 items A B C it will print ABCAB
Hopefully this info will be helpful to you for implementing a solution in php for this, and many future problems. Mod can be really useful for implementing various things when working with arrays
I have a table that contains an unknown number of elements. These represent business sectors.
Array ([0] => 1 [1] => 3 [2] => 6 [3] => 7)
In this case this activity is linked to topics 1, 3, 6 and 7.
I Have some members who have indicated their fields of interest when they register. Interest fields correspond to sectors of activities.
When a new activity is announced, I would like to send an email to members who have checked at least one common interest with the activity. But I have no idea how to retrieve the email of all those members().
In the database the interests of members is stored like this: 246
The first member would be interested in sectors 2, 4 and 6.
I know how to do if there is only one business sector for the activity (example if value = 1).
function readMemberInterest($value) {
$datas = $this->db->select("email")
->from($this->table)
->like('interet',$value,'both');
return $datas;
}
But I do not know where and how to place my LOOP and my OR (is for example value = Array ([0] => 1 1 => 3 [2] => 6 [3] => 7) ).
Briefly, I want to retrieve email members who have at least one common interest with activity. Any tips?
I hope to be clear, my English is not so great ...
Thanks
I have a fixed number recursion set with me. I need to get a random variable and get the left and right values and store them in an array. In the for loop i need to increment the array index...how do i go about it....if the uid is set to 1 it works perfect but in case of any other number i cannnot just get the logic....
the code is like this
// global id
$uid=3; // Take from session.
// Range Array
$range[0]=$uid;
$range[1]=2; // level 0
$range[2]=4; // level 1
$range[3]=8; // level 2
$range[4]=16; // level 3
$range[5]=32; // level 4
$range[6]=64; // level 5
$range[7]=128; // level 6
$range[8]=256; // level 7
$range[9]=512; // level 8
$range[10]=1024; // level 9
$range[11]=2048; // level 10
// Range array ends.
$gar[0]=$uid;
$leftid=$uid*2;
$rightid=($uid*2)+1;
// Generate Levels
//Level 1. 2 Elements
$gar[1]=$leftid;
$gar[2]=$rightid;
// Level 2. 4 elements
// range starts with element 1 of range array.
$i=($gar[1]*2);
$ip=($i+$range[2])-1;
$ak=3;
for($x=$i; $x<=$ip; $x++)
{
$gar[$x-1]=$x;
}
var_dump($gar);
The output is as follows if uid is set to 3. If you see the array key has not incremented..
array (size=7)
0 => int 3
1 => int 6
2 => int 7
11 => int 12
12 => int 13
13 => int 14
14 => int 15