I am working on react js application and building comment reply structure. API is returning me an array of comments, but it's not in a comment hierarchy.
My API response is like this:
review: {_id: 35,email: "test#gmail.com", review: "Shavon B does an AMAZING job!! I had another fant…e taking care of my home. Shavon is a rock star!"}
comments: [
0: {_id: 36, status: 1, email: "neha#shandil.com", review: "Shavon B does an AMAZING job!! I had another fant…e taking care of my home. Shavon is a rock star!", parent_id: 35, reply_to:35}
1: {_id: 37, status: 1, email: "archana#gmail.com", review: " Thank you for sharing your review of your home cl…e taking care of my home. Shavon is a rock star!", parent_id: 35, reply_to:36}
2: {_id: 39, status: 1, email: "radhika#dummy-url.com", review: "Shavon B does an AMAZING job!! I had another fant…e taking care of my home. Shavon is a rock star!", parent_id: 35, reply_to:37}
3: {_id: 40, status: 1, email: "archi#123.com", review: "good", parent_id: 35, reply_to:36}
4: {_id: 41, status: 1, email: "test#test.com", review: "Testing", parent_id: 35, reply_to:35}
]
here parent_id means these are comments for any blog with id 35, and reply_to means this is a reply for that particular comment _id, like array at index 1 is a reply for comment at 0 index.
Now I am also getting a new reply at the end of the list. Now I want to show all comments in their hierarchy.
Now the problem is I am getting a simple array with all comments and replies, how can I show them in the hierarchy.
Is this possible to push HTML in between, please suggest me a solution, I want to show comments up to two levels.
You will need to convert comments to tree structure and will need to write recursive logic to process comments.
Function for converting flat list to the tree:
function unflatten(arr) {
var tree = [],
mappedArr = {},
arrElem,
mappedElem;
// First map the nodes of the array to an object -> create a hash table.
for(var i = 0, len = arr.length; i < len; i++) {
arrElem = arr[i];
mappedArr[arrElem._id] = arrElem;
mappedArr[arrElem._id]['children'] = [];
}
for (var id in mappedArr) {
if (mappedArr.hasOwnProperty(id)) {
mappedElem = mappedArr[id];
// If the element is not at the root level, add it to its parent array of children.
if (mappedElem.parent_id) {
mappedArr[mappedElem['parent_id']]['children'].push(mappedElem);
}
// If the element is at the root level, add it to first level elements array.
else {
tree.push(mappedElem);
}
}
}
return tree;
}
Here is working POC of Recursive Component and tree data in action:
https://stackblitz.com/edit/react-7jhe22?file=index.js
The POC shows automatically adding a random comment to mimic the behavior of user adding a comment.
This also shows how you can append at the end of comments array and still generate comment view with help of unflatten function. Since this is recursive, you can reply to any comment!!
Related
I've been wondering if you could move one field position. For example:
Current Document:
{
user_id: 1234567890,
coins: 100,
card: 500,
username: 'Shin'
}
(Expected Document) I would like to move the username into this:
{
user_id: 1234567890,
username: 'Shin',
coins: 100,
card: 500
}
Query
as far as i know project keeps the order we define
but try it on your driver to be sure, with more fields also
the problem is that we have to do this for all fields (here are only 4 so its ok)
but why you need that? in general because its hash-map we dont care about the order
Playmongo
aggregate(
[{"$project":
{"user_id": "$user_id",
"username": "$username",
"coins": "$coins",
"card": "$card"}}])
I am currently building an iOS application that stores user added products using Google Firestore. Each product that is added is concatenated into a single, user specific "products" array (as shown below - despite having separate numbers they are part of the same array but separated in the UI by Google to show each individual sub-array more clearly)
I use the following syntax to return the data from the first sub-array of the "products" field in the database
let group_array = document["product"] as? [String] ?? [""]
if (group_array.count) == 1 {
let productName1 = group_array.first ?? "No data to display :("`
self.tableViewData =
[cellData(opened: false, title: "Item 1", sectionData: [productName1])]
}
It is returned in the following format:
Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17
However I am trying to query each of the individual sections of this sub array, so for example, I can return "Product Name: 1" instead of the whole sub-array. As let productName1 = group_array.first is used to return the first sub-array, I have tried let productName1 = group_array.first[0] to try and return the first value in this sub-array however I receive the following error:
Cannot infer contextual base in reference to member 'first'
So my question is, referring to the image from my database (at the top of my question), if I wanted to just return "Product Name: 1" from the example sub-array, is this possible and if so, how would I extract it?
I would reconsider storing the products as long strings that need to be parsed out because I suspect there are more efficient, and less error-prone, patterns. However, this pattern is how JSON works so if this is how you want to organize product data, let's go with it and solve your problem.
let productRaw = "Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17"
First thing you can do is parse the string into an array of components:
let componentsRaw = productRaw.components(separatedBy: ", ")
The result:
["Product Name: 1", "Listing Price: 3", "A brief description: 4", "Product URL: 2", "Listing active until: 21/04/2021 10:22:17"]
Then you can search this array using substrings but for efficiency, let's translate it into a dictionary for easier access:
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: ": ")
product[keyVal[0]] = keyVal[1]
}
The result:
["Listing active until": "21/04/2021 10:22:17", "A brief description": "4", "Product Name": "1", "Product URL": "2", "Listing Price": "3"]
And then simply find the product by its key:
if let productName = product["Product Name"] {
print(productName)
} else {
print("not found")
}
There are lots of caveats here. The product string must always be uniform in that commas and colons must always adhere to this strict formatting. If product names have colons and commas, this will not work. You can modify this to handle those cases but it could turn into a bowl of spaghetti pretty quickly, which is also why I suggest going with a different data pattern altogether. You can also explore other methods of translating the array into a dictionary such as with reduce or grouping but there are big-O performance warnings. But this would be a good starting point if this is the road you want to go down.
All that said, if you truly want to use this data pattern, consider adding a delimiter to the product string. For example, a custom delimiter would greatly reduce the need for handling edge cases:
let productRaw = "Product Name: 1**Listing Price: 3**A brief description: 4**Product URL: 2**Listing active until: 21/04/2021 10:22:17"
With a delimiter like **, the values can contain commas without worry. But for complete safety (and efficiency), I would add a second delimiter so that values can contain commas or colons:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
With this string, you can much more safely parse the components by ** and the value from the key by $$. And it would look something like this:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
let componentsRaw = productRaw.components(separatedBy: "**")
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: "$$")
product[keyVal[0]] = keyVal[1]
}
if let productName = product["name"] {
print(productName)
} else {
print("not found")
}
i struggle my had now sine several days with a way to do conditional filter of an object array with another object array.
lack on capabilities to properly abstract here... maybe you ahve some ideas.
I have a given Object Array A but more complex
var ArrA = [{
number: 1,
name: "A"
}, {
number: 2,
name: "C"
}]
And i want to filer for all results matiching id of Object Array B
var ArrB = [{
id: 1,
categorie: "wine"
}, {
id: 3,
categorie: "beer"
}, {
id: 10,
categorie: "juice"
}]
And in the best case moving this directly also together with an if condition.... but i was not able to handle it ... here is where i am now ... which is not working....
let newArray = ArrA.filter{$0.number == ArrB.... }.
if (newArray.count != 0){
// Do something
}
is there a lean way to compare one attribute of every object in an array with one attribute of another every object in an array ?
Lets break this down: You need all arrA objects that matches arrB ids, so first thing first you need to map your arrB to a list of id (because you dont need the other infos)
let arrBid = Set(arrB.map({ $0.id })) // [1, 3, 10]
As commented below, casting it to Set will give you better results for huge arrays but is not mandatory though
Then you just need to filter your first arrA by only keeping object that id is contained into arrBid :
let arrAFilter = arrA.filter({ arrBid.contains($0.number) })
[(number: 1, name: "A")]
and voila
I have an array of objects that I'd like to group by field1 and sum by field2. An example would be a class product that has a title field and a price field.
In an array of products, I have multiple gloves with different prices, and multiple hats with different prices. I'd like to have an array with distinct titles, that aggregate all the prices under the same title.
There's an obvious solution with iterating over the array and using a hash, but I was wondering if there was a "ruby way" of doing something like this? I've seen a lot of examples where Ruby has some unique functionality that applies well to certain scenarios and being a Ruby newbie I'm curious about this.
Thanks
There's a method transform_values added in ruby 2.4 or if you require 'active_support/all', with this you can do something like so:
products = [
{type: "hat", price: 1, name: "fedora"},
{type: "hat", price: 2, name: "sombrero"},
{type: "glove", price: 3, name: "mitten"},
{type: "glove", price: 4, name: "wool"}
]
result = products
.group_by { |product| product[:type] }
.transform_values { |vals| vals.sum { |val| val[:price] } }
# => {"hat"=>3, "glove"=>7}
It's a little unclear to me from the question as asked what your data looks like, so I ended up with this:
Product = Struct.new(:title, :price)
products = [
Product.new("big hat", 1),
Product.new("big hat", 2),
Product.new("small hat", 3),
Product.new("small hat", 4),
Product.new("mens glove", 8),
Product.new("mens glove", 9),
Product.new("kids glove", 1),
Product.new("kids glove", 2)
]
Given that data, this is how I'd go about building a data structure which contains the sum of all the prices for a given title:
sum_by_title = products.inject({}) do |sums, product|
if sums[product.title]
sums[product.title] += product.price
else
sums[product.title] = product.price
end
sums
end
This produces:
{"big hat"=>3, "small hat"=>7, "mens glove"=>17, "kids glove"=>3}
To explain:
Ruby inject takes an initial value and passes that to the iteration block as a "memo". Here, {} is the initial value. The return value from the block is passed into the next iteration as the memo.
The product.title is used as a hash key and the running sum is stored in the hash value. An if statement is necessary because the first time a product title is encountered, the stored value for that title is nil and cannot be incremented.
I probably wouldn't ship this code due to the hidden magic of the hash default value constructor but it's possible to write the same code without the if statement:
sum_by_title = products.inject(Hash.new { 0 }) do |sums, product|
sums[product.title] += product.price
sums
end
Hope you enjoy Ruby as much as I do!
I've 18 documents in my collection movie. For each movie for example:
{
title: "Test Movie 2",
date: [20130808, 20130606],
score: [ {"pete": 1, "mary": 1, "simon": 1, "pat": 2, "mike": 0},
{"pete": 5, "mary": 5, "simon": 5, "pat": 0, "mike": 5}]
}
Now, I want to show the date and sum of the second document in the array 'score' on the client, like:
<div class="details">
Test Movie 2: 20 points 20130606
</div>
Have somebody an idea how I can do that?
You could use a transform, it might be better to define each document with a name and explicitly defining the points as name/value pair instead of points being the value for each persons name.
But this should work:
Movies.find({}, {transform: function(doc) {
var total_points = 0;
var people = doc.score[1]; //Second in array
for(point in people) {
total_points += people[point]
}
doc.points = total_points;
return doc;
}});
This should give you:
{
title: "Test Movie 2",
points: 20,
date: [20130808, 20130606],
score: [ {"pete": 1, "mary": 1, "simon": 1, "pat": 2, "mike": 0},
{"pete": 5, "mary": 5, "simon": 5, "pat": 0, "mike": 5}]
}
Mongo can likely do this outright, but you're not going to be able to do this directly by querying a collection due to limitation of the Mongo livedata package as of 0.6.5. Aggregation framework is also off-limits, and they seem to have pulled the 'hidden' method that allowed direct access to Mongo.
So your options are:
Use transform as per Akshat's answer - the best of both worlds
Aggregate manually in the client in a template helper. I recommend using _.js which comes 'free' with Meteor (again this might change, but you could always pull the library in manually later).
var sum = _.reduce(score[1], function(memo, num){ return memo + num; }, 0);
(I didn't test the above, but it should send you on the right track).
Aggregate upstream, during the insert/update/deletes, likely by observing changes on the collection and 'feeding in' the sum() of the elements you are inserting in either the same collection or an aggregate one.
Which method you use depends on where performance matters most to you, usually doing aggregates before you insert tend to avoid issues later on, and lightens load.
Good luck and let us know how you get on.