How to get duplicate array out of an array - php

I have an array with orders. Example:
$orders = [
'0' => [
'ordernumber' => 1,
'customer' => [
'phone' => '0123456789',
'mobile' => '0612345678'
],
],
'1' => [
'ordernumber' => 2,
'customer' => [
'phone' => '0123456789',
'mobile' => '0612345678'
],
],
'2' => [
'ordernumber' => 3,
'customer' => [
'phone' => '0987654321',
'mobile' => '0687654321'
],
],
'3' => [
'ordernumber' => 3,
'customer' => [
'phone' => '0123456789',
'mobile' => '0612345678'
],
]
];
I want to sort these orders. As you can see there can be orders where the same customer (customer with same phone number, this can be either same phone number or same mobile number) has multiple orders. I want to put all the orders that have the same phone number (doesn't matter if the phone number matches or the mobile number) in an array $duplicateOrders and all the "single" orders (orders that dont match a phone number) in an array $singleOrders. At the end the orders array must be empty. But no order can be lost or be in both arrays.
I have tried to loop through the orders with a foreach loop where I put every order in the $singleOrders array and unset it from the $orders array. I than try to match that order with another foreach loop to all the remaining orders in $orders. If I get a match i put that order (this is done once) in the $duplicateOrders array and every match of it also (I unset every match also from the $orders array). If the orders array is empty I stop, otherwise the first foreach loops kicks in and takes the next order and the proces repeats. This is my code:
protected function splitDuplicateOrders()
{
$singleOrderKey = 0;
if ($this->orders) {
foreach ($this->orders as $key => $order) {
if (count($this->orders) == 0) {
break;
}
array_push($this->singleOrders, $order);
unset($this->orders[$key]);
$orderPushed = false;
foreach ($this->orders as $otherKey => $value) {
if ($order->customer->phone == $value->customer->phone || $order->customer->mobile == $value->customer->mobile) {
if (!$orderPushed) {
array_push($this->duplicateOrders, $order);
}
array_push($this->duplicateOrders, $value);
unset($this->orders[$otherKey]);
unset($this->singleOrders[$singleOrderKey]);
$orderPushed = true;
}
}
$singleOrderKey++;
}
}
}
I expected to have an $duplicateOrders array with all the duplicates and a $singleOrders array with all the singles. I tested this with an $orders array of a total of 4 orders where 2 of them were duplicates and 2 were singles. The function sorted it nicely (but only if the orders aren't right after each other, if they are it still sorts the duplicates right but leaves one also in the $singleOrders and than I have 5 orders). Than I tested it where there were 3 duplicates and 1 single order. The $duplicateOrders array was correct but in the $singleOrders the single order was placed but also one duplicate order from the $orders array. It somehow removed the 2 duplicates correct but left one duplicate in the $singleOrders array.
Can someone help me to debug this or provide a different approach? I have been trying to solve this for 2 days but no success.

You could make use of Laravel Collections, in this case I'm gonna use the partition() method. From the documentation:
partition()
The partition method may be combined with the list PHP function to
separate elements that pass a given truth test from those that do not:
$collection = collect([1, 2, 3, 4, 5, 6]);
list($underThree, $equalOrAboveThree) = $collection->partition(function ($i) {
return $i < 3;
});
$underThree->all();
// [1, 2]
$equalOrAboveThree->all();
// [3, 4, 5, 6]
So in your case:
$orders = /** query or API to get orders as array */;
list($repeated, $single) = collect($orders)
->partition(function ($order) use ($orders) {
return $orders->where('customer.phone', $order['customer']['phone'])->count() > 1
OR $orders->where('customer.mobile', $order['customer']['mobile'])->count() > 1;
});
// now you can use them:
$repeated->all();
$single->all();
Notice that this two newly created objects ($repeated and $single) are in fact also instances of the Collection class (as you can see where I used the all() method on each), so you can keep constraining/sorting/customizing them with the help of the Collection's methods.

Related

How do I get the position of an element in an array in php/laravel

So I have a
student model,
subject model,
marks model, which has the total score field
what I am trying to do is get the total score in an exam for each student in a class and then retrieve the results according to the highest score in the class and then position those scores as 1,2,3...
then finally get the position a single student and store it in an exam record table.
so far I was able to achieve getting the total score in an exam for each student, stored them in an array, sorted them according to the highest score,
the only issue now giving me a headache is getting the position of those scores from the array, my question is how can I get the position of a score for a student, or is there a way to add the positions to the scores and then retrieve the position for a student
For example
1 student_id => 2, total_score => 500
2 student_id => 3, total_score => 455
3 student_id => 5, total_score => 345
here is my code below, Please anyone with an idea how to solve this I need your help.
TextInput::make('position')->numeric(
function (Closure $set) {
// Get all students from class
$studentsInClass = $this->class->students;
//empty array to store student's total_score
$totalScore = [];
// loop through students get all their total_score on all subjects from mark table and sum it, then store it
in totalScore array.
foreach ($studentsInClass as $key => $student) {
$totalScore[] = array('student_id' => $student->id, 'total_score' => $student->marks->sum('total_score') );
}
// Sort scores from highest to lowest
$sortedScores= array_values(array_reverse(Arr::sort($totalScore, function ($value) {
return $value['total_score'];
})));
// get the current student Id
$id = $this->student->id;
// find a student in the array that matches the current student id and return his score.
//so this is where I would like to return the score and position of the student
$filteredArray = Arr::where($sortedScores, function ($value, $key) use ($id) {
return $value['student_id'] == $id;
});
}
)->disabled(),
if you dd($sortedScores)
You have a two-dimensional array.
$sortedScores = [
['student_id' => 2, 'total_score' => 443],
['student_id' => 4, 'total_score' => 410],
['student_id' => 1, 'total_score' => 371],
['student_id' => 3, 'total_score' => 170],
];
The index of each row is already consecutive from 0-3. I think you want to add a rank to the rows.
foreach($sortedScores as $idx => $row)
$sortedScores[$idx]['rank'] = $idx+1;
//test output
var_export($sortedScores);
Output:
array (
0 =>
array (
'student_id' => 2,
'total_score' => 443,
'rank' => 1,
),
1 =>
array (
'student_id' => 4,
'total_score' => 410,
'rank' => 2,
),
2 =>
array (
'student_id' => 1,
'total_score' => 371,
'rank' => 3,
),
3 =>
array (
'student_id' => 3,
'total_score' => 170,
'rank' => 4,
),
)
If you want rank to start at 0, just assign $idx.
Try on https://3v4l.org/9Dv7D
I hope this helps and that I understood your problem correctly.
To find the key for a specific value in a multidimensional array see 'PHP Multidimensional Array Searching (Find key by specific value)'
You can use the array_search() function. This will either return the key of the value you are looking for or false if it is not in the array.
$letters = ['a', 'b', 'c', 'd'];
$key = array_search('b', $letters);
//In this case $key will equal to 2.
Presuming that $filteredArray contains a single item, then the first key should be the student's position in the sorted array.
$position = array_keys($filteredArray)[0];

Get total number of occurrences of a data item from nested array

I have an array with a structure like:
$arr = [
'data1' => [ /* some data */],
'data2' => [
'sub-data1' => [
[
'id' => 1
'status' => 'active'
],
[
'id' => 2
'status' => 'not-active'
]
],
'sub-data2' => [
[
'id' => 3
'status' => 'active'
],
[
'id' => 4
'status' => 'active'
]
]
]
]
Is there a simple way in which I can count how many sub-dataxxx have any item with a status is active?
I have managed to do this with nested foreach loops, but I'm wondering if there is a more simple method?
The above example should show the number of active entries as 2, as there are 2 sub-data elements with active statuses. This ignores any non-active statuses.
Edit: To clarify my expected result
I am not wanting to count the number of status = active occurrences. I'm wanting to count the number of sub-dataxxx elements that contain an element with status = active.
So in this instance, both of sub-data1 and sub-data2 contain sub-elements that contain status = active, therefore my count should be 2.
you can do it quite easily with a function like this
function countActiveSubData(array $data): int
{
return array_reduce($data, function ($res, $sub) {
foreach ($sub as $d) {
if ('active' === $d['status']) {
return $res + 1;
}
}
return $res;
}, 0);
}
you can call it on a single data or if you want to get the result for entire $arr you can call it like this
$result = array_map('countActiveSubData', $arr);
// the result will be [
'data1' => 0,
'data2'=> 2
....
]

How I get data by group User ID for foreach controller function in Laravel

How can I get data by grouping user_id for a foreach loop in a controller's function in Laravel. For example, user_id = 2, 5 rows available, user_id = 1, 10 rows available. Then show 2 arrays.
$lists = lists::wherestatus(1)->groupby('user_id')->get();
foreach($lists as $list){
$list = functionHere;
}
What function can I create for this on the controller for grouping?
I need more information, but based on what you shared, you should be able to do this (removing the foreach):
$lists = Lists::whereStatus(1)->get()->groupBy('user_id');
The difference is that if you use groupBy before get, you are grouping your query by user_id, so instead of getting 5 rows for user_id = 2 and 10 for user_id = 1, you are going to get 2 rows and just the latest data, so you need to use Collection's groupBy.
What you want to do is group all the information by user_id but have each row, a schema like this:
[
'1' => [ // user_id
['user_id' => '1', 'column1' => 'random value'],
['user_id' => '1', 'column1' => 'other value'],
// 8 remaining rows
],
'2' => [ // user_id
['user_id' => '2', 'column1' => 'other nice value'],
// 4 remaining rows
],
]
you should first in List model set:
public function scopeStatus(){
return $this->where('status','1');
}
and in your controller:
$products = List::status()->groupby('user_id')->get();

laravel query,redundant row will be inserted into to tabel invoice particulars.eg FROM table contains 6 rows,after exicuting TO table will 21 rows

$ar = $po_id;
$variableAry=explode(",",$ar);
foreach($variableAry as $var1) {
$details11=DB::table('po_estimations')
->where('po_number',$var1)
->select('*')
->get();
foreach($details11 as $details)
{
$inserts[] = ['invoice_id' => $key,'shade' => $details_>project_shade,'unit' => $details->unit,'In_range' => $details->project_range,'brand_name' => $details->brand_name,'particulars_name' => $details->po_number,];
DB::table('invoice_particulars')->insert($inserts); //saves redundant data
}
}
Like Mihir Bhende states your are constantly adding another element to your array and inserting that array on every iteration.
The following events are currently happening:
loop details
add an array to the end of $inserts
$inserts contains 1 element
insert $inserts
1 element has been inserted in the database
next iteration
add an array to the end of $inserts
$inserts contains 2 elements (1 from the previous iteration)
insert $inserts
2 elements have been inserted in the database
next iteration
add an array to the end of $inserts
$inserts contains 3 elements (2 from the previous iterations)
insert $inserts
3 elements have been inserted in the database
next iteration
...
As you can see the $inserts array is growing bigger because all the previous additions to it remain.
One way to solve this is to put the DB::table(...)->insert($inserts) outside of the loop. If insist on only one query being executed you should implement the following:
foreach($details11 as $details)
{
$inserts[] = ['invoice_id' => $key,'shade' => $details_>project_shade,'unit' => $details->unit,'In_range' => $details->project_range,'brand_name' => $details->brand_name,'particulars_name' => $details->po_number,];
}
DB::table('invoice_particulars')->insert($inserts); //saves redundant data
Otherwise Mihir Bhende answer will do the trick as well.
$inserts[] means to push into the array!
so each time it's executed, it will add a new item to the array.
what you want is:
foreach($details11 as $details)
{
$inserts = ['invoice_id' => $key,'shade' => $details_>project_shade,'unit' => $details->unit,'In_range' => $details->project_range,'brand_name' => $details->brand_name,'particulars_name' => $details->po_number,];
DB::table('invoice_particulars')->insert($inserts); //saves redundant data
}
so just remove the square brackets next to $inserts[] to become $inserts
When you are using insert, you can either create one record or insert multiple :
1. insert one record :
DB::table('users')->insert([
'email' => 'test#something.com',
'name' => 'stackoverflow'
]);
2. insert multiple records :
DB::table('users')->insert([
[
'email' => 'test1#something.com',
'name' => 'stackoverflow1'
],
[
'email' => 'test2#something.com',
'name' => 'stackoverflow2'
],
[
'email' => 'test3#something.com',
'name' => 'stackoverflow3'
]
]);
Now in your case you are doing
$insert[] = ['invoice_id' ....]
which with every iteration in foreach is appending a new value and not assigning it. You can consider this similar to array_push behavior.
By doing this, 6 total values and looping in foreach each time 1 is added : 1 + 2 + 3 + 4 + 5 + 6 = 21 records inserted.
Update it to this :
foreach(explode(",",$ar) as $var1) {
$details11=DB::table('po_estimations')->where('po_number',$var1)->get();
foreach($details11 as $details)
{
DB::table('invoice_particulars')->insert([
'invoice_id' => $key,
'shade' => $details->project_shade,
'unit' => $details->unit,
'In_range' => $details->project_range,
'brand_name' => $details->brand_name,
'particulars_name' => $details->po_number
]);
}
}

Laravel: how to validate that required subarray items are present?

In my laravel POST endpoint, Im expecting an "items" parameter in the following form:
$items => [
['item' => 'a', 'item_slot' => 1],
['item' => 'b', 'item_slot' => 2],
['item' => 'c', 'item_slot' => 3],
]
I want to validate that all the required item slots were provided.
I have an array of required slots $requiredItemSlots = [1, 2]
What validation rules should I use to make sure that item slots in $requiredItemSlots were present? Note, i don't want to limit the provided slots to the required ones, i just need to make sure that the required slots were filled.
For now I have something like:
'items.*.item' => 'required',
'items.*.item_slot' => 'required|distinct'
To ensure that no duplicate slots were passed in.
Originally I tried doing
'items.*.item_slot' => Rule::in($requiredItemSlots)
But that's not correct because not all the slots are necessarily required.
You'll need to make a custom rule, something like that:
'items' => [
'required',
'array',
function ($attribute, $value, $fail) {
$required = [1, 2];
// Cast to collection for easier checks
$items = collect($value);
foreach ($required as $r) {
if (! $items->firstWhere('item_slot', '=', $r)) {
$fail("$r has to be present in items.");
}
}
},
], // ..other validation rules
You may use a combination of distinct, in:1,2,3 and size:3 to validate the input:
'items' => 'required|array|size:3',
'items.*.item_slot' => [
'required',
'distinct',
Rule::in($requiredSlots),
]
With size:3 you force the array to have exactly 3 elements. With distinct you make sure there are no duplicates in the item_slot element field. And with Rule::in($requiredSlots) you ensure that there are no unknown item_slots given.

Categories