In 1998, Leon Venter posted several Omnis code puzzles to the mailing list. The aim was to get a task done within one line, so these puzzles became famous as Leon Venter's One Liners in the Omnis community. The basic idea of a one liner is that you can put it as calculation behind a field and get complex jobs done without the need of tons of procedure codes and control procedures. I often used derivates of Leon's one liners in my code and always was amazed how easily even difficult jobs can be done within one line. The one liners stayed unanswered for some days, thus giving every list member the opportunity to enter the contest and send a solution privately to Leon. After that, Leon provided the solution and would honour the first correct and also the most original solutions.

 


 One Liner #1

For those of you who enjoy this sort of thing, here's the first of several programming challenges, each of which requires writing just a single line of code. The intention is to provide interesting and/or efficient solutions to common Omnis7 and Studio programming problems. The solution will be posted a few days after the challenge.

Description

You're writing a routine that accepts a numeric parameter, pValue, which must fall within a certain range of values, i.e. kMin <= pValue <= kMax. If pValue is outside the required range, it needs to be adjusted. Here's the obvious, but inefficient, solution:

If pValue < kMin
   Calculate pValue as kMin
Else if pValue > kMax
   Calculate pValue as kMax
End if

Challenge

Write one line of code that performs the same operation as above, i.e. Calculate pValue as ...

(Bonus points if you can do it without using the '<' and '>' operators.)

Solution:

Well, Ekkehard nailed that one in record time! Honorable mention goes to Steve Howard and Mischa Klement for their suggestions. Here are some of the many possible solutions:

Good:

Calculate pValue as pick((pValue<kMin) + 2*(pValue>kMax), pValue, kMin, kMax)

Better:

Calculate pValue as pick(pValue>kMax, pick(pValue<kMin, pValue, kMin), kMax)

Best:

Calculate pValue as max(kMin, min(pValue, kMax))

The 'Best' solution is more compact and approx. 23% faster than the 'Obvious' solution.

 


 One Liner #2

or: Doing SQL-type joins on lists

Description:

To find all customers who have placed at least one order, you could execute a SQL query similar to this:

SELECT Customer.* FROM Customer, Order WHERE Customer.cust_id = Order.cust_id 

This causes the database server to perform an inner join on the two tables. Omnis list variables are similar to database tables in that they consist of rows and columns. It would therefore be useful to be able to perform the same operation on list variables as well, but unfortunately, Omnis doesn't support relational list operators.

Of course, you can 'brute force' it by stepping through the Customer list and searching the Order list for each Customer, but there is a more convenient and efficient way to do it.

The Challenge:

Given two lists, e.g.:

fCustomers: 
fCustId fCustName fCustPhone ...
001 John Doe 415-555-1212
002 Jane Public 650-555-1234
003 Abbey Normal 408-555-1357
004 H. Sapiens 510-555-1235
005 A. N. Other 609-555-1248

and fOrders:
fOrdId fOrdCustId fOrdDate ...
1023 003 3/14/97
1024 002 4/23/97
1025 005 5/26/97
1026 003 6/15/97
1027 003 8/21/97
1028 002 10/11/97

Complete the following procedure so that it selects all lines in fCustomers whose fCustId appear in fOrdCustId in fOrders (i.e. lines 2, 3 and 5 in this example).

Set current list fCustomers Set search as calculation ____  ;; <-- Supply the search expression 
Search list (From start,Select matches (OR),Deselect non-matches (AND))

Solution:

The simplest solution is:

totc(fOrders, fOrdCustId = fCustId)

This does a totc() on the fOrders list for each fCustId in fCustomers. In this case, totc() will return a non-zero value (1 for one order, 2 for two orders, etc.) if any matches are found in fOrders, which causes the current fCustomer line to be selected.

The totc() function is much faster than using the 'Search list' command to scan the fOrders list because it doesn't bother setting #L as it checks each line.

Congrats to David Barnett for the fastest response! He gets extra credit for pointing out that I should done a 'select distinct...' in my SQL example. Good job! Also, kudos to the following Listers for their correct responses: Reg Paling, Ekkehard Gentz, Jeff Rink, Stephen Lanza, Jonathan Rumbold, Terry Young, and Jim Pistrang. It appears that that one was too easy...

 


 One Liner #3

Description:

Your window contains a toolbar. Under certain conditions, you need to disable all the controls on the toolbar. OMNIS Studio makes this simple with $sendall(), but Omnis7 doesn't support that notational function. Or does it?

The Challenge:

Using Omnis7, write a single line of code that disables all controls in a window's toolbar.

Bonus:

Write a single line of code that disables all pushbuttons on a window. It shouldn't affect other window object types, and should work regardless of changes to tab order or the number of pushbuttons. (In other words, "Disable fields x to y" won't cut it.)

Extra bonus:

Modify the line of code above so that, in addition to disabling the pushbuttons, it also changes their background color to kDarkBlue (or whatever).

Solution:

The award for One-liners #3 goes to two people:

1) Eric Azarcon, for the fastest response to the toolbar problem (two solutions!)
2) Mischa Klement, for the first response with a complete set of solutions.

Disabling controls in a window toolbar:
Eric Azarcon, Steve Finger and Joe Maus each came up with this solution:

Calculate $cwind.$toolgroup.$controls.0.$enabled as kFalse 

which is better than the one I had in mind:

Calculate #F as $cwind.$toolgroup.$controls.$makelist($ref.$enabled.$assign(kFalse)) 

Good job, guys!

Disabling pushbuttons:

Calculate #F as $cwind.$objs.$makelist($ref.$enabled.$assign(pick($ref.$objtype=kPushbutton, $ref.$enabled, kFalse)))

FYI, I received a couple of solutions similar to this:

Calculate #F as $cwind.$objs.$makelist($ref.$enabled.$assign($ref.$objtype<>kPushbutton) 

Note that this enables every window object that isn't a pushbutton, which might not be desirable.

Disabling and coloring pushbuttons:

Calculate #F as $cwind.$objs.$makelist($ref.$enabled.$assign(pick($ref.$objtype=kPushbutton, $ref.$enabled, kFalse)), $ref.$backcolor.$assign(pick($ref.$objtype=kPushbutton, $ref.$backcolor, kDarkBlue))) 

The interesting feature in this case is obviously the nature of $makelist(). It's simply a looping construct that iterates through a group, evaluating the specified expression(s) once for each group member. In that sense, it's similar to Studio's $sendall() feature.

I received solutions to all three problems from Mischa Klement, Eric Azarcon, Reg Paling, Brian O'Sullivan, Terence Young, Udo Sonnabend & Markus from WIGASoft, and Joe Maus. Steve Finger sent a solution to the first problem. Congrats to all!

p.s. My apologies to Fred Haislmaier for not including his name in the list of people who responded correctly to One-liners #2

#3 was tough. #4 will be a bit easier...

Terence Young deserves special mention for his rather novel approach, which is based on the left-to-right execution of terms in an expression. While his solutions weren't the optimal ones in this case, they are interesting and will no doubt be useful in other situations. They are:

1)

Calculate #F as ($cwind.$toolgroup.$controls.$appendlist([#L3.$cols.$add('#1D0')],$ref.$ ident))+(totc(#L3,$cwind.$toolgroup.$controls.[#1D0].$enabled.$assign(kF alse))) 

2)

Calculate #F as #L4.$cols.$add('#1D0')+(#L4.$cols.$add('#S2'))+($cwind.$objs.$appendlist (#L4,$ref.$ident,$ref.$objtype))+(totc(#L4,$cwind.$objs.[#1D0*(pos('push button',#S2)>0)].$enabled.$assign(kFalse))) 

3)

Calculate #F as #L4.$cols.$add('#1D0')+(#L4.$cols.$add('#S2'))+($cwind.$objs.$appendlist (#L4,$ref.$ident,$ref.$objtype))+(totc(#L4,$cwind.$objs.[#1D0*(pos('push button',#S2)>0)].$enabled.$assign(kFalse)))+(totc(#L4,$cwind.$objs.[#1D0 *(pos('pushbutton',#S2)>0)].$backcolor.$assign(kDarkBlue)))

 


 One Liner #4

Description:

One of your windows displays a list of items. The associated list variable, fItems, is defined as ( fItemName, fItemDescr ). You only have enough room in the list field to display fItemName, but the user has to be able to see fItemDescr when necessary. The user doesn't like horizontal scrolling or popup dialogs, but will accept the displaying of the item descriptions in the window's status bar.

The Challenge:

Write a 'Help message' command for the list field that displays the corresponding fItemDescr for each line in fItems as the user moves the cursor over the list. (Note: no mouseclicks or keypresses involved)

Solution:

Plenty of responses to this one! Ekkehard's showed up first. The solutions are:

Help message [fItems(2, mouseover(kMLine))] or 
Help message [fItems(nam(fItemDescr), mouseover(kMLine))] or
Help message [lst(fItems, mouseover(kMLine), fItemDescr)]

I also received correct responses from (in order): Mischa Klement, Steve Knouse, Joe Maus, Lars Schaerer, Caryn Rudy, Reg Paling, Terry Young, Eric Azarcon, Geir Fjaerli, Gavin Foster, Jim Pistrang, and Paul W. Mulroney.

Contrary to what some people suggested, you do NOT have to enable $mouseevents for this to work. And, it works just fine for table fields too. #5 will be more challenging...

 


 One Liner #5

Description:

You have a list, fParts, defined as (fPartId, fPartName, fPartCost).

Challenge:

Write a single line of code that produces a tab-delimited string containing the list data. For example, if the list contained this:

D017 Widget   10.49 
G293 Frobnitz 5.83
... ... ...

the string would contain this:

D017 <tab> Widget <tab> 10.49 <crlf> G293 <tab> Frobnitz <tab> 5.83 <crlf> ... 

(No extensions allowed.)

Solution:

Here's the general solution:

Calculate #F as #S1.$assign(") + totc(fParts,#S1.$assign(con(#S1,fPartId,chr(9),fPartName,chr(9),fPartCos t,chr(13,10)))) 

Three things to note here:

1) The $assign() function can be used to change the value of a regular variable _within_ an expression. The first $assign() above simply initializes the output variable, while the second one concatenates the column values for each row.

2) totc() is to lists as $makelist() is to notational groups. In other words, you can use totc() to process the rows in a list in much the same way as you use $makelist() to process the members of a notational group.

3) Unlike other languages, the Omnis chr() function accepts a comma-delimited list of ASCII codes.

Note: In the solution above, I used #S1 as a character variable simply because it's convenient and universally known. However, this does not constitute an endorsement of the use of hash vars ...

Although this was a tricky one, several people were up to the challenge: Terence Young was the first to respond, followed by (in order): Fred Haislmaier, August Zumbuhl, Eric Azarcon, Arnie Skeels, and Mischa Klement. The Defensive Programming award goes to Terence and Arnie for including the initialization of the output variable. Way to go!

Some interesting variations:

Although it wasn't requested and no-one submitted it, here's a slightly more generic solution for Studio:

Calculate #F as myRow.$assign(row('')) + fParts.$sendall(myRow.$assigncols(con(myRow.1,$ref.1,chr(9),$ref.2,chr(9 ),$ref.3,chr(13,10)))) 

In this solution, the column names are generic, but their number is still hard-coded. Also, the output var has to be a Row variable. (Unfortunately, for some reason, $sendall() fails when using a regular character var as the output var.)

Terry Young submitted this expression for generating the result and displaying it in a calculated field or an OK message:

pick(totc(fParts,//%%str//.$assign(con(%%str,fPartId,chr(9),fPartName,ch r(9),fPartCost,chr(13,10))))>0,'',%%str) 

August Zumbuhl came up with a generic routine that, given any list, returns a tab-delimited string (I modified it slightly):

Parameter pList (Field name) 
Local variable lFields (List)
Local variable lFieldName (Character 10000000)
Local variable lFieldDelim (Character 10000000)
Local variable lExpr (Character 10000000) = 'con('
Local variable lDummy (List)
Local variable lStream (Character 10000000) = ''

Begin reversible block
Set current list lDummy
End reversible block

Copy list definition pList ;; Save current list def because Redefine isn't reversible
Calculate lFields as pList.$cols.$makelist(con('%%c',$ref.$ident),pick($ref.$ident=pList.$col count,',chr(9),',',chr(13,10))'))
;; Generate the required number of local char vars

Set current list lFields
Redefine list {lFieldName,lFieldDelim}
Set current list pList
Redefine list {^lFields}
;; Redefine input list so we can access its column values

Calculate #F as totc(lFields,lExpr.$assign(con(lExpr,lFieldName,lFieldDelim))) ;; Build con()
Calculate #F as totc(pList,lStream.$assign(con(lStream,evalf(lExpr)))) ;; Generate result
Copy list definition lDummy ;; Restore list def before returning

Set return value {lStream}

#6 to follow ...

 


 One Liner #6

Description:

You have a window that displays two list fields, named List1 & List2. The list fields are of equal height. You want their vertical scrolling to be synchronized, i.e. if the user scrolls List1, then List2 should also scroll, so as to display the same range of lines.

Challenge:

Supply the missing line in the field control procedure for List1:

If #VSCROLLED
;; ???
End if

Solution:

The solutions are:

Queue scroll (Down) {[$cwind.$objs.List2.$order] ($cobj.$firstvis-$cwind.$objs.List2.$firstvis)} ;; or: 
Queue scroll (Up) {[$cwind.$objs.List2.$order] ($cwind.$objs.List2.$firstvis-$cobj.$firstvis)}

Since $firstvis is not assignable, the trick was to find a command that can change its value for you. Note that it isn't necessary to know the names of the list variables associated with the list fields**, which is why the problem description didn't refer to them.

(** Unfortunately, the Omnis terminology is confusing -- I used the term 'list field' to refer to the list window object because that's what Omnis displays when you double-click on a list on a window. However that name is rather similar to 'data field', which is the variable associated with the window object.)

Of course, by putting a similar command in the FCP of List2, you can synch the scrolling of List1 too.

Congrats to Mischa Klement, who was the first to respond. He was followed by Terence Young, Geir Fjaerli, August Zumbuhl, and Fred Haislmaier. No-one got it exactly right. The suggested solutions looked like this:

Queue scroll (Down) {List2 ($cwind.$objs.List1.$firstvis-$cwind.$objs.List2.$firstvis)}
Queue scroll (Up) {List2 ($cwind.$objs.List2.$firstvis-$cwind.$objs.List1.$firstvis)}

As all the respondents pointed out, the first 'List2' is the name of the list variable, whereas the following 'List2' and 'List1' are the names of the window objects. The problem with these solutions is that they require the name of the list field (i.e. the window object) to match that of its list variable. Because of this, they will fail if the two lists are based on the same list variable (which is quite probable).

The moral of the story is this: When referring to window objects, avoid using a hard-coded variable name or tab order whenever possible -- use the object name instead.

Studio makes this easier because its commands generally accept object names. BTW, Geir, August and Fred also suggested solutions based on the use of $firstsel, which I hadn't considered. While this approach is not as 'elegant', it's certainly creative, which makes it just as interesting. It makes use of Omnis' automatic scrolling of selected lines and looks like this:

Calculate $cwind.$objs.List2.$firstsel as $cwind.$objs.List1.$firstvis + ($cwind.$objs.List1.$lastvis-$cwind.$objs.List1.$firstvis)/2 

# 7 to follow...

 


 One Liner #7

or: Implementing an associative array

Description:

You have a list named Items, which is defined as (Id, Desc). Id is unique.

Sample data:

'CA', 'California' 
'NY', 'New York'
'DC', 'District of Columbia'

The Challenge:

Supply an expression that returns the associated Desc for a given Id. In other words, complete this command:

Calculate Field2 as ... ;; Field1 holds the Id

(Hint: Add an extra column when building the Items list.)

Bonus:

Same as the above, but without changing the original list definition in any way.

Extra bonus:

Same as the above, but display 'Not specified' if Field1 is empty.

Solution:

My apologies for not posting this sooner. It's been a busy couple of weeks... 

By adding a numeric column, LineNum, which holds the line number for each line in the list, you can do this:

Calculate Field2 as Items(nam(Desc), totc(Items, LineNum * (Id = Field1))) ;or: 
Calculate Field2 as Items(nam(Desc), totc(Items,pick(Id = Field1, 0 ,LineNo)))

This makes for a nice, simple solution but it's limited because, for each line, the value of LineNum must match #L for that line. In other words, once you've built the list, you can't change the row order or insert/delete rows, except at the end of the list. (See below for a more general solution.)

BTW, when building the list, you can use #LN to conveniently generate the LineNum values, e.g.:

Define list (Id, Desc, LineNum) 
Add line to list ('CA', 'California', #LN) ;; LineNum = 1
Add line to list ('DC', 'District of Columbia', #LN) ;; LineNum = 2 etc.

Bonus: Same as the above, but without changing the original list definition in any way.

This approach is far more useful since it allows for any and all changes to the list at any time. I was surprised by the number of different solutions I received... Here's a solution that was inspired by an idea from Gavin Foster. It's similar to the one above, except that it generates the line numbers on the fly:

Calculate Field2 as Items(nam(Desc), %line.$assign(0) * 0 + totc(Items, %line * (Id = Field1) * %line.$assign(%line + 1))) 

A number of people submitted the following solution:

Calculate #F as Field2.$assign('') + totc(Items, Field2.$assign(con(Field2, pick(Id <> Field1, Desc)))) 

which generates the correct result, but doesn't exactly satisfy the problem requirement because the expression returns a numerical value. The intention was to have a function which returns the Desc value for a given Id, thereby allowing it to be used in a calculated field on a window or report.

Note that you can use a jst() or pick() to coerce such expressions into returning the desired value, e.g.:

Calculate Field2 as jst(Field2.$assign('') + totc(Items, Field2.$assign(con(Field2, pick(Id <> Field1, Desc)))), 'X0', Field2, '') 

Kelly Burgess, Fred Haislmaier and Sid Mitra all submitted this gem:

Calculate #F as totc(Items, Field2.$assign(pick(Id = Field1, Field2, Desc))) 

which truly is a thing of beauty! It reminds me of logic gate array design... After a suitable transformation, this becomes:

Calculate Field2 as pick(not(totc(Items, Field2.$assign(pick(Id = Field1, Field2, Desc)))), Field2) 

which is a winner. Most triumphant, guys! Rudolf Bargholz came up with a nifty solution for Studio:

Calculate Field2 as Items.$first(Items.$search(Id = Field1)).Desc 

Here's another Studio solution:

Calculate Field2 as lst(Items, Items.$search(Id = Field1, 1, 0, 0, 0), Desc) 

Extra bonus: Same as the above, but display 'Not specified' if Field1 is empty. This was a trick question! No-one provided the solution I was looking for, namely:

Add line to list ('', 'Not specified') ;; Done right after building the Items list. 

This solution doesn't require any changes to the lookup expression above, since the data is doing the work for you. (Note that you weren't required to handle the 'Not found' case!) OK, I know I'm cheating a bit here because you could argue that this approach requires two lines (namely, the 'Add line to list' and 'Calculate Field2 as ...'), but the data-driven approach is certainly worth keeping in mind when the situation allows for it...

I really like Kelly's solution:

Calculate Field2 as pick(not(Field2.$assign('Not specified') + totc(Items, Field2.$assign(pick(Id = Field1, Field2, Desc)))), Field2) 

Arnie Skeels' solution for Studio looks like this:

Calculate Field2 as pick(Items.$search(Id = Field1, 1, 0, 0, 0) > 0, 'Not specified', Items(nam(Desc), Items.$search(Id = Field1))) 

David Barnett's solution uses a sign change to control its execution:

Calculate Field2 as jst(%line.$assign(0) + totc(Items, %line.$assign(pick((Id = Field1) + 2 * (%line < 0), %line + 1, -(%line + 1), %line))), 'X0', pick(%line > 0, lst(Items, abs(%line), Desc), 'Not specified'), '') 

Most of the other solutions were similar to this:

Calculate #F as Field2.$assign('') + totc(Items, Field2.$assign(con(Field2, pick(Id <> Field1, Desc)))) + Field2.$assign(pick(Field2='', Field2, 'Not specified')) 

or this:

Calculate #F as pick(len(Field1)=0, Field2.$assign('') + totc(Items, Field2.$assign(con(Field2, pick(Id <> Field1, Desc)))), Field2.$assign('Not specified')) 

Some responders went beyond the challenge requirements to handle the 'Not found' case as well:

Calculate #F as pick(len(Field1)=0, pick(totc(Items, Id=Field1)=0, Field2.$assign('') + totc(Items, Field2.$assign(con(Field2, pick(Id <> Field1, Desc)))), Field2.$assign('Not found')), Field2.$assign('Not specified')) 

Here's a hybrid solution which also does the job:

Add a line to your lookup list by doing this:

Add line to list ('', 'Not specified') 

Then use a slightly-modified version of Kelly's solution for your lookups:

Calculate Field2 as pick(not(Field2.$assign('Not found') + totc(Items, Field2.$assign(pick(Id = Field1, Field2, Desc)))), Field2) 

Congrats to Daniel Weinstein for the quickest response. He was followed by: Kelly Burgess, David Barnett, Rudolf Bargholz, August Zumbuhl, Fred Haislmaier, Sid Mitra, Terry Young, Ekkehard, Steve Knouse, Mischa Klement, and Arnie Skeels.

Unfortunately, due to work demands, I won't be able to post more one-liners for a while. (I actually only have a couple left.) However, I'm happy to see that others have posted some, and I know that Gavin Foster, Daniel Weinstein, and others have more up their sleeves. Presenting these challenges has been very enjoyable.

Although I've been chided (gently!) for consuming people's valuable free time, I can't promise I won't do it again... My thanks to all who participated, especially: Terry Young, for his contributions to the Omnis knowledge pool and for being the most conscientious responder. Kelly Burgess, the highest-quality coder and nicest person in the Omnis universe, for his thought-provoking comments and questions. And Gavin Foster, Mischa Klement, Ekkehard Gentz, Fred Haislmaier, August Zumbuhl, Eric Azarcon, Reg Paling & Arnie Skeels for their ingenuity and enthusiastic participation.

Summary:

The main intention of these challenges was to demonstrate how you can, when necessary, execute 'mini-programs' in a single expression. Your tools for doing this are:

- totc(), $makelist(), and $sendall() for looping constructs 
- pick() for conditional branching
- con(), jst(), pick(), and + for sequencing operations
- $assign() and $assigncols() for assigning values to variables and attributes

Like microcoding, it's not everyone's cup of tea, but it can be very useful at times. I hope you all learnt as much as I did!

Go to top
JSN Boot template designed by JoomlaShine.com