Reactive Programming with RxJS and separating data into - arrays

I am trying to work more reactively with Angular 15 and RxJS observables for a UI component. I only subscribe to the data in my component template (html). I have a service that receives data from an external system. The issue I have is the data may be received for multiple days and needs to be 'split' for the display usage.
In the display, there are individual components of data, that show the rows returned from the service call. The service makes an HTTP call to an external host.
this.Entries$ = this.Http_.get<Array<IEntry>>('http://host.com/api/entry');
This data is then an array of records with an EntryDate, and a structure of information (UserId, Description, TimeWorked, etc.). The external API sends all the records back as one flat array of data which is not guaranteed to be sorted, it comes back in a database order, which was the order records were entered. A sort might be needed for any processing, but I am not sure.
[
{ "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 2.5, ... },
{ "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 4.5, ... },
{ "EnterDate": 20221025, "UserId": "BSmith", "TimeWorked": 5, ... },
{ "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 4, ... },
{ "EnterDate": 20221026, "UserId": "BSmith", "TimeWorked": 5, ... },
{ "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 2, ... },
]
Currently, my HTML template loops through the Entries$ observable, when it was for just one day.
<ng-container *ngFor="let OneEntry of (Entries$ | async)">
<one-entry-component [data]=OneEntry />
</ng-container>
I want to be able to split my array of records into different datasets by EntryDate (and apparently user, but just EntryDate would work for now), similar to the groupBy(), but I do not know how to get to the internal record references, as it would be a map within the groupBy() I believe.
With the data split, I would then be looking to have multiple one-day-components on the page, that then have the one-entry-component within them.
|---------------------------------------------------------------|
| |
| |-One Day 1-------------###-| |-One Day 2-------------###-| |
| | | | | |
| | [ One Line ] | | [ One Line ] | |
| | [ One Line ] | | [ One Line ] | |
| | [ One Line ] | | [ One Line ] | |
| | [ One Line ] | | [ One Line ] | |
| | | | | |
| |---------------------------| |---------------------------| |
| |
| |-One Day 3-------------###-| |-One Day 4-------------###-| |
| | | | | |
| | [ One Line ] | | [ One Line ] | |
| | [ One Line ] | | [ One Line ] | |
| | [ One Line ] | | [ One Line ] | |
| | [ One Line ] | | [ One Line ] | |
| | | | | |
| |---------------------------| |---------------------------| |
| |
|---------------------------------------------------------------|
The 4 boxes would be there if there were 4 separate days in the response. If there were 2 different dates, then just show 2 dates, but this could be 5 or 6 even.
I would need an Observable that had the dates for splitting (and even users) and then be able to pass this as data to the one<one-day-component [data]=OneDateOneUser$ />. My component needs this so that I can count the time entries for the title, which I believe is a simple .pipe(map()) operation.
Within the one-day-component, I would then simply loop through the OneDateOneUser$ observable to extract individual records to send to the one-entry-component as I do currently.
I believe the RxJS groupBy is what I need. However, I am new to RxJS, and working with the inner array of data is not clear to me in the example.
If the data is individual records like the example, and not an array of data, then it does work using the example RxJS reference.
import { of, groupBy, mergeMap, reduce, map } from 'rxjs';
of(
{ id: 1, name: 'JavaScript' },
{ id: 2, name: 'Parcel' },
{ id: 2, name: 'webpack' },
{ id: 1, name: 'TypeScript' },
{ id: 3, name: 'TSLint' }
).pipe(
groupBy(p => p.id, { element: p => p.name }),
mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], [`${ group$.key }`]))),
map(arr => ({ id: parseInt(arr[0], 10), values: arr.slice(1) }))
)
.subscribe(p => console.log(p));
// displays:
// { id: 1, values: [ 'JavaScript', 'TypeScript' ] }
// { id: 2, values: [ 'Parcel', 'webpack' ] }
// { id: 3, values: [ 'TSLint' ] }
However, simply changing the data in the of() to be an array (more like how my data comes back), breaks, and I am not sure how to fix it:
import { of, groupBy, mergeMap, reduce, map } from 'rxjs';
of(
[
{ id: 1, name: 'JavaScript' },
{ id: 2, name: 'Parcel' },
{ id: 2, name: 'webpack' },
{ id: 1, name: 'TypeScript' },
{ id: 3, name: 'TSLint' }
]
).pipe(
groupBy(p => p.id, { element: p => p.name }),
mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], [`${ group$.key }`]))),
map(arr => ({ id: parseInt(arr[0], 10), values: arr.slice(1) }))
)
.subscribe(p => console.log(p));

What if you just turned that Array<IEntry> into a Record<number, IEntry> with something like lodash's group by and a map RxJS operator?
Then you can get the desired outcome with some flex-wrap and flex-row functionality on the template and just loop over the entries of the record:
Check this little working CodePen
import {groupBy} from 'lodash'
const fakeData = [
{ "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 2.5, ... },
{ "EnterDate": 20221025, "UserId": "JohnDoe", "TimeWorked": 4.5, ... },
{ "EnterDate": 20221025, "UserId": "BSmith", "TimeWorked": 5, ... },
{ "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 4, ... },
{ "EnterDate": 20221026, "UserId": "BSmith", "TimeWorked": 5, ... },
{ "EnterDate": 20221026, "UserId": "JohnDoe", "TimeWorked": 2, ... },
]
// Replace "of" with your API call
entriesByDate$: Observable<Record<number, IEntry>> = of(fakeData).pipe(
map(allEntries => groupBy(allEntries, 'EnterDate'))
)
<div *ngIf="entriesByDate$ | async as entries" class="flex flex-row flex-wrap">
<ng-container *ngFor="let [enterDate, entries] of Object.entries(entries)">
<entry-group-component [title]="enterDate" [data]="entries" />
</ng-container>
</div>
No need to import lodash if you care to write the grouping function yourself. Array#reduce should suffice:
function groupByEnterDate(entries: Array<IEntry>) {
return entries.reduce(
(acc, current) => {
const key = current.EnterDate
const groupedByKey = acc[key] ?? []
return { ...acc, [key]: [...groupedByKey, current] }
},
{}
)
}

Related

OVER PARTITION equivalent in MongoDB

I've simplified the scenario for brevity.
The initial data:
| EngineerId | FirstName | LastName | BirthdateOn | CupsOfCoffee | HoursOfSleep |
| ---------- | --------- | -------- | ----------- | ------------ | ------------ |
| 1 | John | Doe | 1990-01-01 | 5 | 8 |
| 2 | James | Bond | 1990-01-01 | 1 | 6 |
| 3 | Leeroy | Jenkins | 2000-06-20 | 16 | 10 |
| 4 | Jane | Doe | 2000-06-20 | 8 | 2 |
| 5 | Lorem | Ipsum | 2010-12-25 | 4 | 5 |
db.engineers.insertMany([
{ FirstName: 'John', LastName: 'Doe', BirthdateOn: ISODate('1990-01-01'), CupsOfCoffee: 5, HoursOfSleep: 8 },
{ FirstName: 'James', LastName: 'Bond', BirthdateOn: ISODate('1990-01-01'), CupsOfCoffee: 1, HoursOfSleep: 6 },
{ FirstName: 'Leeroy', LastName: 'Jenkins', BirthdateOn: ISODate('2000-06-20'), CupsOfCoffee: 16, HoursOfSleep: 10 },
{ FirstName: 'Jane', LastName: 'Doe', BirthdateOn: ISODate('2000-06-20'), CupsOfCoffee: 8, HoursOfSleep: 2 },
{ FirstName: 'Lorem', LastName: 'Ipsum', BirthdateOn: ISODate('2010-12-25'), CupsOfCoffee: 4, HoursOfSleep: 5 }
])
We want to see:
the cups of coffee consumed by the engineer
the row number sorted descending by cups of coffee
the count of engineers with the same birthdate
the sum of coffees consumed by engineers with a common birthdate
the average hours of sleep for engineers with a common birthdate
The SQL query is:
SELECT
FirstName,
LastName,
BirthdateOn,
CupsOfCoffee,
ROW_NUMBER() OVER (PARTITION BY BirthdateOn ORDER BY CupsOfCoffee DESC) AS 'Row Number',
COUNT(EngineerId) OVER (PARTITION BY BirthdateOn) AS TotalEngineers,
SUM(CupsOfCoffee) OVER (PARTITION BY BirthdateOn) AS TotalCupsOfCoffee,
AVG(HoursOfSleep) OVER (PARTITION BY BirthdateOn) AS AvgHoursOfSleep
FROM Engineers
Resulting in the following:
| FirstName | LastName | BirthdateOn | Row Number | CupsOfCoffee | TotalEngineers | TotalCupsOfCoffee | AvgHoursOfSleep |
| --------- | -------- | ----------- | ---------- | ------------ | -------------- | ----------------- | --------------- |
| John | Doe | 1990-01-01 | 1 | 5 | 2 | 6 | 7 |
| James | Bond | 1990-01-01 | 2 | 1 | 2 | 6 | 7 |
| Leeroy | Jenkins | 2000-06-20 | 1 | 16 | 2 | 24 | 6 |
| Jane | Doe | 2000-06-20 | 2 | 8 | 2 | 24 | 6 |
| Lorem | Ipsum | 2010-12-25 | 1 | 4 | 1 | 4 | 5 |
I've done quite a bit of reading on the MongoDB Aggregate Pipeline, but haven't been able to find a good solution yet. I understand that this is not SQL and the solution might not yield results in this exact format (although that would be amazing). One thing I've considered is combining the results of an aggregate and the collection, but that's either not possible or I've been searching with the wrong terms. $merge looked promising, but AFAIU it would modify the original collection and that's no good.
I've gotten as far as the following, but the results do not include the "row number", cups consumed by specific engineers, or IDs and names of the engineers.
db.engineers.aggregate([
{
$group: {
_id: '$BirthdateOn',
TotalEngineers: {
$count: { }
},
TotalCupsOfCoffee: {
$sum: '$CupsOfCoffee'
},
AvgHoursOfSleep: {
$avg: '$HoursOfSleep'
}
}
}
])
My thought with combining would be to find all of the engineers and then run the aggregate and "join" it to the engineers by BirthdateOn.
Thank you for any help! It's much appreciated.
You did a good start. To get the input data you can use with the $push operator.
Would be this:
db.engineers.aggregate([
{
$group: {
_id: "$BirthdateOn",
TotalEngineers: { $count: {} },
TotalCupsOfCoffee: { $sum: "$CupsOfCoffee" },
AvgHoursOfSleep: { $avg: "$HoursOfSleep" },
data: { $push: "$$ROOT" }
}
}
])
Regarding proper output try:
db.engineers.aggregate([
{
$group: {
_id: "$BirthdateOn",
TotalEngineers: { $count: {} },
TotalCupsOfCoffee: { $sum: "$CupsOfCoffee" },
AvgHoursOfSleep: { $avg: "$HoursOfSleep" },
data: { $push: "$$ROOT" }
}
},
{ $unwind: "$data" },
{ $replaceWith: { $mergeObjects: ["$$ROOT", "$data"] } }
])
Often it is pointless to run $group and afterwards $unwind which basically revert the former operation.
MongoDB version 5.0 introduced the $setWindowFields stage, which is quite similar to the SQL Windowing function:
I think it would be this one:
db.engineers.aggregate([
{
$setWindowFields: {
partitionBy: "$BirthdateOn",
sortBy: { CupsOfCoffee: 1 },
output: {
TotalEngineers: { $count: {} },
TotalCupsOfCoffee: { $sum: "$CupsOfCoffee" },
AvgHoursOfSleep: { $avg: "$HoursOfSleep" },
"Row Number": { $documentNumber: {} }
}
}
}
])

Reading nested JSON containing Arrays with Timeseries in pandas dateframe

I know there are most likely many questions like this, but i could not find an answer.
I have a json object that contains time Series data and i have trouble loading it into a Dataframe.
The data contains timesieres data like this:
{
"categories": [
"02.01.2007",
"03.01.2007",
"04.01.2007",
...
],
unixtime": [
"1167696000",
"1167782400",
"1167868800",
...
],
"series": [
{
"au": [
{
"name": "Gold",
"data": [
15.51,
15.48,
...
],
"color": "#FFD200"
}
],
"ag": [
{
"name": "Silber",
"data": [
315.21,
313.97,
...
],
"color": "#FFD200"
}
],
...
]
}
]
}
All in all there is price data from 7 metals (au,ag,pt,pd,rh,ir,ru). categories contians a list of time stamps, unixtime as well in a different format, Than comes the series object itself, which is nested and contains Objects which each have a field data that contains the prices i want to convert into pandas Time series data.
My code is however not working
import requests
import pandas as pd
r = # http request
j = r.json()
data = pd.json_normalize(data=j)
is not working. i gues i have to use the record_path keyword but i do not understand it. I wand a DataFrame that looks like this:
| Categories | unixtime | au | ag | ... |
1| 02.01.2007 | 1167696000 | 15.51 | 315.21 | ... |
2| 03.01.2007 | 1167782400 | 15.48 | 313.97 | ... |
3| ...
thank you in advance!

how to grep the values from mongodb

New to development. I am trying to grep the values from JSON file. Can some one help me on this.
[{
"State": "New York",
"City": "Queens",
"Cars": {
"gas": {
"USAMade": {
"Ford": ["Fordcars", "Fordtrucks", "Fordsuv"]
},
"OutsideUS": {
"Toyota": ["Tcars", "Ttrucks", "TSUV"]
}
},
"electric": {
"USAMade": {
"Tesla": ["model3", "modelS", "modelX"]
},
"OutsideUS": {
"Nissan": ["Ncars", "Ntrucks", "NSUV"]
}
}
}
},
{
"State": "Atlanta",
"City": "Roswell",
"Cars": {
"gas": {
"USAMade": {
"Ford": ["Fordcars", "Fordtrucks", "Fordsuv"]
},
"OutsideUS": {
"Toyota": ["Tcars", "Ttrucks", "TSUV"]
}
},
"electric": {
"USAMade": {
"Tesla": ["model3", "modelS", "modelX"]
},
"OutsideUS": {
"Nissan": ["Ncars", "Ntrucks", "NSUV"]
}
}
}
}
]
How to list the type of cars like ( gas/electric)?
once i get the type, i want to list the respective country of made ( USAMade/OutsideUS).
After that i want to list the models ( Ford/Toyota)?
Lets suppose you have the documents in the file test.json , here it is how to grep using linux shell tools cat,jq,sort,uniq:
1) cat test.json | jq '.[] | .Cars | keys[] ' | sort | uniq
"electric"
"gas"
2) cat test.json | jq '.[] | .Cars[] | keys[] ' | sort | uniq
"OutsideUS"
"USAMade"
3) cat test.json | jq '.[] | .Cars[][] | keys[] ' | sort | uniq
"Ford"
"Nissan"
"Tesla"
"Toyota"
If your data is in mongoDB , I suggest you keep this distinct values in single document in separate collection and populate the frontend page on load from this collection and the document can look something like this:
{
State:["Atlanta","Oregon"],
City:["New York" , "Tokio" , "Moskow"],
Location:["OutsideUS" ,"USAMade"],
Model:["Ford","Toyota","Nissan"]
}
You don't need to extract distinct values from database every time your front page loads since it is not scalable solution and at some point it will become performance bottleneck ...
But if you want it anyway to get only the distinct keys from mongoDB based on selection you can do as follow:
1.
mongos> db.test.aggregate([ {"$project":{"akv":{"$objectToArray":"$Cars"}}} ,{$unwind:"$akv"} ,{ $group:{_id:null , "allkeys":{$addToSet:"$akv.k"} } }] ).pretty()
{ "_id" : null, "allkeys" : [ "gas", "electric" ] }
mongos> db.test.aggregate([ {"$project":{"akv":{"$objectToArray":"$Cars.gas"}}} ,{$unwind:"$akv"} ,{ $group:{_id:null , "allkeys":{$addToSet:"$akv.k"} } }] ).pretty()
{ "_id" : null, "allkeys" : [ "USAMade", "OutsideUS" ] }
mongos> db.test.aggregate([ {"$project":{"akv":{"$objectToArray":"$Cars.gas.USAMade"}}} ,{$unwind:"$akv"} ,{ $group:{_id:null , "allkeys":{$addToSet:"$akv.k"} } }] ).pretty()
{ "_id" : null, "allkeys" : [ "Ford" ] }

How to use one collection to map another collection in MongoDB

I have a dataset like the following
[{id:1,month:1,count:1},{id:1,month:2,count:2},{id:1,month:3,count:3}......,
{id:2,month:1,count:1},{id:2,month:2,count:2},{id:2,month:3,count:3}.......,
........
........
{id:19,month:1,count:1},{id:19,month:2,count:2},{id:19,month:3,count:3}.......,]
and the table looks like this.
|id|month|count|
|1 | 1 | 1 |
.............
.........
|19| 12 | 4 |
there is another id as divisonId and it maps to the ids above as the following
{1:[1,2,4,5],2:[3,6,8,9],3:[7,10,....19]}
and the mapping table looks like this.
|divisionId| id|
| 1 | 1 |
| 1 | 2 |
| 1 | 4 |
| 1 | 5 |
| 2 | 3 |
| 2 | 6 |
......
......
so now I need to aggreate the data and sum and regroup them according to the divisonIds.
so eventually the return data should look like the following
[{divsionId:1,month:1,count:19},{divisionId:1,month:2,count:53},{divisionId:1,month:3,count:66}......,
{divisionId:2,month:1,count:21},{divisionId:2,month:2,count:82},{divisionId:2,month:3,count:63}.......,
{divisionId:3,month:1,count:1},{divisionId:3,month:2,count:2},{divisionId:3,month:3,count:3}.......,]
and the table should looks like
| divisionId| month | count |
| 1 | 1 | 200 |
| 1 | 2 | 400 |
| 1 | 3 | 300 |
.....
.....
| 3 | 11 | 500 |
| 3 | 12 | 600 |
so basically, it just map the ids to divisionId, and sum up the individually months across those ids and aggregate a new collection to return data.
I am not allowed to put divisionId to the original table, due to the fact that ids maybe assigned to different divisionIds in the future, or it could have been much easier to just use the aggregate methods.
currently, one way I can do so is to use Javascript to get datas for ids separately according to the mapping, then do the calculation and push it up to mongos to store it as a new collection, so when UI query the data in the future, it will just read the query, saving the expensive calculation. But it would be awesome if I can solve this problem just by using some advanced mongodb syntax. Please let me know if you have some tricks I could use. thanks.
Please try this :
db.divisionIdCollName.aggregate([{
$lookup:
{
from: "idCollectionName",
let: { ids: "$id" },
pipeline: [
{
$match:
{
$expr:
{ $in: ["$id", "$$ids"] }
}
}
],
as: "data"
}
}, { $unwind: { path: "$data", preserveNullAndEmptyArrays: true } },
{ $group: { _id: { divisionId: '$divisionId', month: '$data.month' }, month: { $first: '$data.month' }, count: { $sum: '$data.count' } } },
{$addFields : {divisionId : '$_id.divisionId'}}, {$project : {_id:0}}
])
Result : Mongo playground
You can test results over there - Once you feel aggregation is returning correct results try to add $merge stage to write result to another collection, you could use $out instead of $merge but we're using $merge is because if given name matches with any collection name in database $out will replace entire collection with aggregation result each time query runs which is destructive & should not be used if this query has to update existing records in a collection, Which is why we're using $merge, Please read about those two before you use, So add below stage as last stage after $project.
Note : $merge is new in v 4.2, $out >= v 2.6. If you're doing $merge since you're specifying two fields on: [ "divisionId", "month" ] - So there should be an unique compound index created on collection divisionIdCollNameNew - So yes we need to manually create collection & create unique index as well & then execute query.
Create collection & index :
db.createCollection("divisionIdCollNameNew")
db.divisionIdCollNameNew.createIndex ( { divisionId: 1, month: 1 }, { unique: true } )
Final Stage :
{ $merge : { into: { coll: "divisionIdCollNameNew" }, on: [ "divisionId", "month" ], whenNotMatched: "insert" } }
ASSUMPTION
Collection months has this structure: {id:1,month:1,count:1}
Collection divisions has this structure: {1:[1,2,4,5],2:[3,6,8,9],3:[7,10,19]}
You may perform such query:
db.divisons.aggregate([
{
$addFields: {
data: {
$filter: {
input: {
$objectToArray: "$$ROOT"
},
cond: {
$isArray: "$$this.v"
}
}
}
}
},
{
$unwind: "$data"
},
{
$lookup: {
from: "months",
let: {
ids: "$data.v"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$id",
"$$ids"
]
}
}
}
],
as: "months"
}
},
{
$unwind: "$months"
},
{
$group: {
_id: {
divisionId: "$data.k",
month: "$months.month"
},
count: {
$sum: "$months.count"
}
}
},
{
$project: {
_id: 0,
divisionId: "$_id.divisionId",
month: "$_id.month",
count: "$count"
}
},
{
$sort: {
divisionId: 1,
month: 1
}
}
])
MongoPlayground
EXPLANATION
Your divisions collection has not normalized key:value pairs, so our first step is to convert 1:[...], 2:[...] pair into [{k:"1", v:[...]}, {k:2, v:[...]}] pairs with $objectToArray operator.
Then, we flatten array from previous step with $unwind and apply $lookup with uncorrelated sub-queries to cross with months collection.
The last steps, we $group by divisionId + month and sum count value.
In order to store the result inside another collection, you need to use $out or $merge operator.

JQ - return one array for multiple nested JSON arrays

I have a JSON structure that has repeated keys per message. I would like to combine these into one array per message.
[
{
"id": 1,
"PolicyItems": [
{
"accesses": [
{
"isAllowed": true,
"type": "drop"
},
{
"isAllowed": true,
"type": "select"
}
],
"groups": [],
"users": ["admin"]
}
]
},
{
"id": 2,
"PolicyItems": [
{
"accesses": [
{
"isAllowed": true,
"type": "drop"
}
{
"isAllowed": true,
"type": "update"
}
],
"groups": [],
"users": [
"admin",
"admin2"
]
}
]
}]
I have this:
cat ranger_v2.json | jq -r '[.[] | {"id", "access_type":(.policyItems[].accesses[] | .type)}]'
But this outputs:
[
{
"id": 1,
"access_type": "drop"
},
{
"id": 1,
"access_type": "select"
},
{
"id": 2,
"access_type": "drop"
},
{
"id": 2,
"access_type": "update"
}
]
However, what I want is to output:
[{
"id": 1,
"access_type": ["drop|select"]
},
{
"id": 2,
"access_type": ["drop|update"]
}]
Any ideas how I could do this? I'm a bit stumped!
The values could be 'drop' and 'select', but equally could be anything, so I don't want to hard code these.
Let's start by observing that with your input, the filter:
.[]
| {id, access_type: [.PolicyItems[].accesses[].type]}
produces the two objects:
{
"id": 1,
"access_type": [
"drop",
"select"
]
}
{
"id": 2,
"access_type": [
"drop",
"update"
]
}
Now it's a simple matter to tweak the above filter so as to produce the desired format:
[.[]
| {id, access_type: [.PolicyItems[].accesses[].type]}
| .access_type |= [join("|")] ]
Or equivalently, the one-liner:
map({id, access_type: [[.PolicyItems[].accesses[].type] | join("|")]})
I found something that I can work with.
If I wrap the query with []...
cat ranger_v2.json | jq -r '[.[] | {"id", "access_type":([.policyItems[].accesses[] | .type])}]'
... it produces this type of output:
[
{
"id": 1,
"access_type": ["drop","select"]
},
{
"id": 2,
"access_type": ["drop","update"]
}
]
I can then use the following:
(if (."access_type" | length > 0 ) then . else ."access_type" = [""] end )]
and
(."access_type" | #tsv)
Before I can convert to #csv and use sed to replace the tab with a pipe.
#csv' | sed -e "s/[\t]\+/|/g"
It may not be the most economical way of getting what I need, but it works for me. (Please let me know if there's a better way of doing it.)
cat ranger_v2.json | jq -r '[.[] | {"id", "access_type":([.policyItems[].accesses[] | .type])}] | .[] | [(if (."access_type" | length > 0 ) then . else ."access_type" = [""] end )] | .[] | [.id, (."access_type" | #tsv)] | #csv' | sed -e "s/[\t]\+/|/g"

Resources