Showing posts with label list. Show all posts
Showing posts with label list. Show all posts

Thursday, August 23, 2018

Looking up shapes in a lists of lists

Leave a Comment

Program description

  1. Aim of the program

My program is meant to calculate locations of shapes in a 20X15 sized plane. I have a list of shapes carrying the shape type, its id, its radius or height, and its prospective [X,Y] location on the plane. I have a different list of binary operations carrying only the shape type, its id, and its location relationship with another shape. With this information in the operations list, I should compute the [X,Y] locations of the shapes: Below is a description of the two lists:

List of shapes

I have a list of shapes: each shape is a list of the form:

[[shape, id],height/radius, [X,Y]]

A list of such shapes would look something like the below when it is printed out by Prolog:

[[[diamond,1],4,[_7948,_7954]],[[circle,3],6,[_7894,_7900]],[[square,1],4,[_7840,_7846]],[[circle,1],5,[_7786,_7792]]|_7800] 

List of operations

A list of operations that should be carried out on the shapes each operation is of the form:

[[[circle,1],below,[square,1]]] 

This means that circle 1 should appear below square 1 on the X,Y plane

Such a list when printed out by prolog would look something like the following:

[[[circle,1],below,[square,1]]|_8016] 
  1. The program

So I have computeShapeLocations/2. Its first argument is a list of operations and the second list is a list of shapes. It recursively goes over the list of operations getting the shape ids on both sides of the operation. eg circle 1 - below - sqaure 1 and sends the two shapes to the correct function to calculate the locations using CLPFD. For two shapes with a relative positioning of 'below' I use computeShapesBelow/2 which takes two shapes each of the form [[shape, id],height/radius, [X,Y]].

Steps in ComputeShapeLocations/2: 1. Get an operation of the form [[[circle,1],below,[square,1]]] from the list of operations 2. Fetch first id (circle 1), then type of relationship (below) then second id (square 1). 3. Fetch the shapes from the shapes list (ShapesOut) 4. Send the shapes to computeShapesBelow/2. This just uses clpfd to compare radius or height and the dimensions of my X,Y plane.

:- use_module(library(clpfd)). computeShapeLocations([],_ShapesOut). computeShapeLocations([Operation|Rest],ShapesOut) :- writeln(ShapesOut),                                                                                                                    writeln([Operation|Rest]),                                                                                                                    nth0(0,Operation,Subject1),                                                                                                                   nth0(1,Operation,below),                                                                                                                 nth0(2,Operation,Subject2),                                                                                                                   Shape1 = [Subject1,H,Loc],                                                                                                                Shape2 = [Subject2,H2,Loc2],                                                                                                              member(Shape1,ShapesOut),                                      member(Shape2,ShapesOut),                                                                                                        writeln(Shape1),                                                                                                                  writeln(Shape2),                                                                                                  writeln(Subject1),                                                                                                                 writeln(Subject2),                                      computeShapeBelow(Shape1,Shape2),                                                                          computeShapeLocations(Rest,ShapesOut). computeShapeBelow(Shape1,Shape2) :- nth0(2,Shape1,Location1),                                                                         nth0(2,Shape2,Location2),                                                                                                                 writeln(Shape1),                                                                                                                  writeln(Shape2),                                                                                                                 nth0(1,Shape1,Dim1),                                                                                                              nth0(1,Shape2,Dim2),                                                                                                              nth0(0,Location1,Xcord1),                                                                                                                 nth0(0,Location2,Xcord2),                                                                                                             nth0(1,Location1,Ycord1),                                                                                                             nth0(1,Location2,Ycord2),                                                                                                          Ycord1 #> Dim1, Ycord1 #< 15-Dim1,                                                                                                             Xcord1 #> Dim1, Xcord1 #< 20-Dim1,                                                                                                             Ycord2 #> Dim2, Ycord2 #<  15-Dim2,                                                                                                            Xcord2 #> Dim2, Xcord2 #<  20-Dim2,                                                                                                            Ycord2 #> Ycord1+Dim2+Dim1. 

The problem: In computeShapeLocations/2 my lookup is just bizarre( see step three above in steps of computeShapeLocations/2). I use member(ShapeId, ListOFshapesList) to fetch shapes from listofshapes given their ids [shape,id]. I then print out the results( writeln(Shape1), writeln(Shape2))and the image below shows just how the behavior is wrong. For the first shape (circle,1), the result is good and computeShapesBelow/2 even comes up with a proper limit of its X,Y location (6..14 and 6..9). For the second shape (Shape2 or square 1). It does not behave as expected and the clpfd limits result in lower infinities.

The reason is because this second search of [square,1] ignores an entry of [[square, 1], 4, [_2166, _2172]] which is in the list and instead somehow adds an extra [[square, 1], _2250, [_2262|...]] which it then uses to mess up my results. enter image description here

1 Answers

Answers 1

In my opinion, the source of your problem is being obscured by two simple problems. I don't have all your code and I don't really know what you're trying to do, so I'll just talk about what I see and how I would proceed.

The first problem is that you are not making effective use of unification. For instance, you can replace this:

nth0(0,Operation,Subject1), nth0(1,Operation,below), nth0(2,Operation,Subject2), 

With this:

[Subject1,below,Subject2] = Operation, 

But, moreover, you don't really need Operation on its own, so you can move that into the head of your clause:

computeShapeLocations([[Subject1,below,Subject2]|Rest],ShapesOut) :- 

As you start to make these changes your code will contract quite a bit and it should become a lot easier to see what is going on. What would make it even easier to understand would be using less listy representations. For instance, it's a little easier for me to understand what is going on in this command list:

[below(circle(1), square(1)), below(circle(2), square(1)), ...] 

or even this, which you can do by adding an :- op declaration:

[circle(1) below square(1), circle(2) below square(1), ...] 

and then your pattern matches will look even simpler, like:

compute(Shape1 below Shape2) :- ... 

Similarly, for your shapes, it would be a little easier to understand what is going on if you have a little more structure:

shape(circle(1), 4, X@Y) 

is a little more obvious to me than

[[circle,1], 4, [X,Y]] 

I find it a little odd that you've got unbound variables inside your input list. I gather you're hoping they'll obtain values later on. I suppose there's nothing wrong with this approach, I'm just surprised to see a mixture of ground and nonground acting as inputs.

Your second source of trouble is that you're mixing several kinds of procedure together. I'm pretty sure you have a DCG parsing step going on somewhere. By parsing into this weak, listy representation in there, you're forcing yourself to do more work inside these methods destructuring your lists and obtaining their meaning. Consider:

command([Shape1,below,Shape2]) --> shape(Shape1), "below", shape(Shape2). 

versus

command(Shape1 below Shape2) --> shape(Shape1), "below", shape(2). 

Or,

shape_type(circle) --> "circle".  shape_type(square) --> "square". shape_name(shape(Name, Size, X@Y)) -->      shape_type(T), integer(ID),      integer(Size),      integer(X), integer(Y),      { Name =.. [T, ID] }. 

versus whatever you have now.

IOW, you could create structure during the parse that will simplify your life during the processing. Similarly, doing a lot of what looks to me like debug I/O is making your processing more complex.

find_shape(ShapeId, Shapes, Shape) :-     Shape = shape(ShapeId, _, _),     member(Shape, Shapes).  computeShapeLocations([], _). computeShapeLocations([ShapeId1 below ShapeId2|Rest], Shapes) :-     find_shape(ShapeId1, Shapes, Shape1),     find_shape(ShapeId2, Shapes, Shape2),     computeShapeBelow(Shape1, Shape2),     computeShapeLocations(Rest, Shapes).  computeShapeBelow(shape(_, D1, X1@Y1), shape(_, D2, X2@Y2)) :-     Y1 #> D1, Y1 #< 15 - D1,     X1 #> D1, X1 #< 20 - D1,     Y2 #> D2, Y2 #< 15 - D2,     X2 #> D2, X2 #< 20 - D2,     Y2 #> Y1 + D2 + D1. 

I think if I were staring at this I would find it a bit easier to debug.

Read More

Monday, May 28, 2018

Nested lambda statements when sorting lists

Leave a Comment

I wish to sort the below list first by the number, then by the text.

lst = ['b-3', 'a-2', 'c-4', 'd-2']  # result: # ['a-2', 'd-2', 'b-3', 'c-4'] 

Attempt 1

res = sorted(lst, key=lambda x: (int(x.split('-')[1]), x.split('-')[0])) 

I was not happy with this since it required splitting a string twice, to extract the relevant components.

Attempt 2

I came up with the below solution. But I am hoping there is a more succinct solution via Pythonic lambda statements.

def sorter_func(x):     text, num = x.split('-')     return int(num), text  res = sorted(lst, key=sorter_func) 

I looked at Understanding nested lambda function behaviour in python but couldn't adapt this solution directly. Is there a more succinct way to rewrite the above code?

7 Answers

Answers 1

There are 2 points to note:

  • One-line answers are not necessarily better. Using a named function is likely to make your code easier to read.
  • You are likely not looking for a nested lambda statement, as function composition is not part of the standard library (see Note #1). What you can do easily is have one lambda function return the result of another lambda function.

Therefore, the correct answer can found in Lambda inside lambda.

For your specific problem, you can use:

res = sorted(lst, key=lambda x: (lambda y: (int(y[1]), y[0]))(x.split('-'))) 

Remember that lambda is just a function. You can call it immediately after defining it, even on the same line.

Note #1: The 3rd party toolz library does allow composition:

from toolz import compose  res = sorted(lst, key=compose(lambda x: (int(x[1]), x[0]), lambda x: x.split('-'))) 

Note #2: As @chepner points out, the deficiency of this solution (repeated function calls) is one of the reasons why PEP-572 is being considered.

Answers 2

We can wrap the list returned by split('-') under another list and then we can use a loop to handle it:

# Using list-comprehension >>> sorted(lst, key=lambda x: [(int(num), text) for text, num in [x.split('-')]]) ['a-2', 'd-2', 'b-3', 'c-4'] # Using next() >>> sorted(lst, key=lambda x: next((int(num), text) for text, num in [x.split('-')])) ['a-2', 'd-2', 'b-3', 'c-4'] 

Answers 3

lst = ['b-3', 'a-2', 'c-4', 'd-2'] res = sorted(lst, key=lambda x: tuple(f(a) for f, a in zip((int, str), reversed(x.split('-'))))) print(res)  ['a-2', 'd-2', 'b-3', 'c-4'] 

Answers 4

lst = ['b-3', 'a-2', 'c-4', 'd-2'] def xform(l):     return list(map(lambda x: x[1] + '-' + x[0], list(map(lambda x: x.split('-'), lst)))) lst = sorted(xform(lst)) print(xform(lst)) 

See it here I think @jpp has a better solution, but a fun little brainteaser :-)

Answers 5

you could convert to integer only if the index of the item is 0 (when reversing the splitted list). The only object (besides the result of split) which is created is the 2-element list used for comparison. The rest are just iterators.

sorted(lst,key = lambda s : [x if i else int(x) for i,x in enumerate(reversed(s.split("-")))]) 

As an aside, the - token isn't particularly great when numbers are involved, because it complicates the use of negative numbers (but can be solved with s.split("-",1)

Answers 6

In general with FOP ( functional oriented programming ) you can put it all in one liner and nest lambdas within one-liners but that is in general bad etiquette, since after 2 nesting function it all becomes quite unreadable.

The best way to approach this kind of issue is to split it up in several stages:

1: splitting string into tuple:

lst = ['b-3', 'a-2', 'c-4', 'd-2'] res = map( lambda str_x: tuple( str_x.split('-') ) , lst)    

2: sorting elements like you wished :

lst = ['b-3', 'a-2', 'c-4', 'd-2'] res = map( lambda str_x: tuple( str_x.split('-') ) , lst)   res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) )  

Since we split the string into tuple it will return an map object that will be represented as list of tuples. So now the 3rd step is optional:

3: representing data as you inquired:

lst = ['b-3', 'a-2', 'c-4', 'd-2'] res = map( lambda str_x: tuple( str_x.split('-') ) , lst)   res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) )  res = map( '-'.join, res )   

Now have in mind that lambda nesting could produce a more one-liner solution and that you can actually embed a non discrete nesting type of lambda like follows:

a = ['b-3', 'a-2', 'c-4', 'd-2'] resa = map( lambda x: x.split('-'), a) resa = map( lambda x: ( int(x[1]),x[0]) , a)  # resa can be written as this, but you must be sure about type you are passing to lambda  resa = map( lambda x: tuple( map( lambda y: int(y) is y.isdigit() else y , x.split('-') ) , a)   

But as you can see if contents of list a arent anything but 2 string types separated by '-' , lambda function will raise an error and you will have a bad time figuring what the hell is happening.


So in the end, i would like to show you several ways the 3rd step program could be written:

1:

lst = ['b-3', 'a-2', 'c-4', 'd-2'] res = map( '-'.join,\              sorted(\                    map( lambda str_x: tuple( str_x.split('-') ) , lst),\                        key=lambda x: ( int(x[1]), x[0] )\               )\          ) 

2:

lst = ['b-3', 'a-2', 'c-4', 'd-2'] res = map( '-'.join,\         sorted( map( lambda str_x: tuple( str_x.split('-') ) , lst),\                 key=lambda x: tuple( reversed( tuple(\                             map( lambda y: int(y) if y.isdigit() else y ,x  )\                         )))\             )\     )  # map isn't reversible 

3:

res = sorted( lst,\              key=lambda x:\                 tuple(reversed(\                     tuple( \                         map( lambda y: int(y) if y.isdigit() else y , x.split('-') )\                     )\                 ))\             ) 

So you can see how this all can get very complicated and incomprehensible. When reading my own or someone else's code i often love to see this version:

res = map( lambda str_x: tuple( str_x.split('-') ) , lst) # splitting string  res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) ) # sorting for each element of splitted string res = map( '-'.join, res ) # rejoining string   

That is all from me. Have fun. I've tested all code in py 3.6.


PS. In general, you have 2 ways to approach lambda functions:

mult = lambda x: x*2   mu_add= lambda x: mult(x)+x #calling lambda from lambda 

This way is useful for typical FOP,where you have constant data , and you need to manipulate each element of that data. But if you need to resolve list,tuple,string,dict in lambda these kind of operations aren't very useful, since if any of those container/wrapper types is present the data type of elements inside containers becomes questionable. So we would need to go up a level of abstraction and determine how to manipulate data per its type.

mult_i = lambda x: x*2 if isinstance(x,int) else 2 # some ternary operator to make our life easier by putting if statement in lambda  

Now you can use another type of lambda function:

int_str = lambda x: ( lambda y: str(y) )(x)*x # a bit of complex, right?   # let me break it down.  #all this could be written as:  str_i = lambda x: str(x)  int_str = lambda x: str_i(x)*x  ## we can separate another function inside function with () ##because they can exclude interpreter to look at it first, then do the multiplication   # ( lambda x: str(x)) with this we've separated it as new definition of function   # ( lambda x: str(x) )(i) we called it and passed it i as argument.   

Some people call this type of syntax as nested lambdas, i call it indiscreet since you can see all.

And you can use recursive lambda assignment:

def rec_lambda( data, *arg_lambda ):       # filtering all parts of lambda functions parsed as arguments      arg_lambda = [ x for x in arg_lambda if type(x).__name__ == 'function' ]        # implementing first function in line     data = arg_lambda[0](data)        if arg_lambda[1:]: # if there are still elements in arg_lambda          return rec_lambda( data, *arg_lambda[1:] ) #call rec_lambda     else: # if arg_lambda is empty or []         return data # returns data    #where you can use it like this   a = rec_lambda( 'a', lambda x: x*2, str.upper, lambda x: (x,x), '-'.join)  >>> 'AA-AA'  

Answers 7

I think* if you are certain the format is consistently "[0]alphabet [1]dash" following indexes beyond [2:] will always be number, then you can replace split with slice, or you can use str.index('-')

sorted(lst, key=lambda x:(int(x[2:]),x[0]))  # str.index('-')  sorted(lst, key=lambda x:(int(x[x.index('-')+1 :]),x[0]))  
Read More

Sunday, December 17, 2017

django rest django list query customize json array result response because of date formating

Leave a Comment

I have this Django REST API that I want to customize the list query result for the json response. The reason is because of date formatting and potentially other formatting as well.

This is the Rest API, the issue is created_at I want it to formatted like this: ('%Y-%m-%d %H:%M'). The following code doesnt have any formatting, it will just list and create a json on the result.

@api_view(['POST']) def employee_get_list_by_page(request):     val_params = ["id", "username","first_name","last_name","created_at"]         employee_list = Employee.objects.all().values(*val_params).order_by('id')        page = request.GET.get('page', request.POST['page'])     paginator = Paginator(employee_list, request.POST['page_limit'])      try:         employees = paginator.page(page)     except PageNotAnInteger:         employees = paginator.page(request.POST['page'])     except EmptyPage:         employees = paginator.page(paginator.num_pages)      return Response(list(employees), status=status.HTTP_200_OK)     

This is the model. Notice I have .as_dict() function. For individual record like using emp = Employee.objects.get(id=6), I can do like this emp.as_dict() and the result will have the formatted date in created_at.

class Employee(models.Model):     user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee')     company = models.ForeignKey(Company)     username = models.CharField(max_length=30, blank=False)         first_name = models.CharField(max_length=30, blank=False)     last_name = models.CharField(max_length=30, blank=False)         created_at = models.DateTimeField(auto_now_add=True)      def __str__(self):         return self.user.username      def as_dict(self):         return {"id": "%d" % self.id,                 "username": self.username if self.username else "",                 "first_name": self.first_name if self.first_name else "",                 "last_name": self.last_name if self.last_name else "",                               "created_at":self.created_at.strftime('%Y-%m-%d %H:%M')} 

This is the json response result of the list. Notice the date is not formatted.

[   {     "id": 7,     "username": "mick",     "first_name": "zack",     "last_name": "ray",     "created_at": "2017-12-07T10:09:28.376427Z" <-- I want this to be ('%Y-%m-%d %H:%M')   },   {     "id": 8,     "username": "hu",     "first_name": "rar",     "last_name": "baw",     "created_at": "2017-12-10T09:08:27.473997Z"   }   ] 

Question: How can I have a json list response with the formatting I want ?

2 Answers

Answers 1

Use serializers of django rest framework, create a serializer class

from rest_framework import serializers  class EmployeeSerializer(serializers.ModelSerializer):     created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M')      class Meta:        model = Employee        fields = ("id", "username", "first_name", "last_name", "created_at") 

Now parse your employees queryset using serializer class.

@api_view(['POST']) def employee_get_list_by_page(request):      employees = Employee.objects.all().values(*val_params).order_by('id')     serializer = EmployeeSerializer(employees, many=True)      # rest of your code     ...      return Response(serializer.data, status=status.HTTP_200_OK)   

Format strings may either be Python strftime formats which explicitly specify the format, or the special string iso-8601, which indicates that ISO 8601 style datetimes should be used. (eg 2013-01-29T12:34:56.000000Z)

Answers 2

You can try to define time formt setting in project settings.py file,there are 4 types of time format setting:DATE_FORMAT、DATETIME_FORMAT、TIME_FORMAT 、 SHORT_DATETIME_FORMAT.

for example:

DATE_FORMAT = 'Y-m-d H:i' 

if you use DRF, You also can use this method to define project global date format settings.

You can get ref from https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-DATETIME_FORMAT

Read More

Sunday, September 17, 2017

Managing multiple versions of object in JSON

Leave a Comment

I have a class in C#, that has a number of variables. Let's call it "QuestionItem". I have a list of this object, which the user modifies, and then sends it via JSON serialization (with Newtonsoft JSON library) to the server. To do so, I deserialize the objects that are already in the server, as a List<QuestionItem>, then add this new modified object to the list, and then serialize it back to the server.

In order to display this list of QuestionItems to the user, I deserialize the JSON as my object, and display it somewhere.

Now, the problem is - that I want to change this QuestionItem and add some variables to it.

But I can't send this NewQuestionItem to the server, because the items in the server are of type OldQuestionItem.

How do I merge these two types, or convert the old type to the new one, while the users with the old version will still be able to use the app?

3 Answers

Answers 1

You are using an Object Oriented Language, so you might aswell use inheritance if possible.

Assuming your old QuestionItem to be:

[JsonObject(MemberSerialization.OptOut)] public class QuestionItem  {     [JsonConstructor]     public QuestionItem(int Id, int Variant)     {         this.Id = Id;         this.Variant = Variant;     }      public int Id { get; }     public int Variant { get; }     public string Name { get; set; } } 

you can extend it by creating a child class:

[JsonObject(MemberSerialization.OptOut)] public class NewQuestionItem : QuestionItem {     private DateTime _firstAccess;      [JsonConstructor]     public NewQuestionItem(int Id, int Variant, DateTime FirstAccess) : base(Id, Variant)     {         this.FirstAccess = FirstAccess;     }     public DateTime FirstAccess { get; } } 

Note that using anything different than the default constructor for a class requires you to use the [JsonConstructor] Attribute on this constructor and every argument of said constructor must be named exactly like the corresponding JSON properties. Otherwise you will get an exception, because there is no default constructor available.

Your WebAPI will now send serialized NewQuestionItems, which can be deserialized to QuestionItems. In fact: By default, JSON.NET as with most Json libraries, will deserialize it to any object if they have at least one property in common. Just make sure that any member of the object you want to serialize/desreialize can actually be serialized.

You can test the example above with the following three lines of code:

var newQuestionItem = new NewQuestionItem(1337, 42, DateTime.Now) {Name = "Hello World!"}; var jsonString = JsonConvert.SerializeObject(newQuestionItem); var oldQuestionItem = JsonConvert.DeserializeObject<QuestionItem>(jsonString); 

and simply looking at the property values of the oldQuestionItem in the debugger.

So, this is possible as long as your NewQuestionItem only adds properties to an object and does neither remove nor modify them.

If that is the case, then your objects are different and thus, requiring completely different objects with a different URI in your API, as long as you still need to maintain the old instance on the existing URI.

Which brings us to the general architecture:

The most clean and streamline approach to what you are trying to achieve is to properly version your API.

For the purpose of this link I am assuming an Asp.NET WebApi, since you are handling the JSON in C#/.NET. This allows different controller methods to be called upon different versions and thus, making structural changes the resources your API is providing depending on the time of the implementation. Other API will provide equal or at least similar features or they can be implemented manually.

Depending on the amount and size of the actual objects and potential complexity of the request- and resultsets it might also be worth looking into wrapping requests or responses with additional information. So instead of asking for an object of type T, you ask for an Object of type QueryResult<T> with it being defined along the lines of:

[JsonObject(MemberSerialization.OptOut)] public class QueryResult<T> {     [JsonConstructor]     public QueryResult(T Result, ResultState State,              Dictionary<string, string> AdditionalInformation)     {         this.Result = result;         this.State = state;         this.AdditionalInformation = AdditionalInformation;     }      public T Result { get; }     public ResultState State { get; }     public Dictionary<string, string> AdditionalInformation { get; } }  public enum ResultState : byte {     0 = Success,     1 = Obsolete,     2 = AuthenticationError,     4 = DatabaseError,     8 = .... } 

which will allow you to ship additional information, such as api version number, api version release, links to different API endpoints, error information without changing the object type, etc.

The alternative to using a wrapper with a custom header is to fully implement the HATEOAS constraint, which is also widely used. Both can, together with proper versioning, save you most of the trouble with API changes.

Answers 2

How about you wrapping your OldQuestionItem as a property of QuestionItem? For example:

public class NewQuestionItem {     public OldQuestionItem OldItem { get; set; }     public string Property1 {get; set; }     public string Property2 {get; set; }     ... } 

This way you can maintain the previous version of the item, yet define new information to be returned.

Koda

Answers 3

You can use something like

public class OldQuestionItem {   public DateTime UploadTimeStamp {get; set;} //if less then DateTime.Now then it QuestionItem    public string Property1 {get; set; }   public string Property2 {get; set; }   ...    public OldQuestionItem(NewQuestionItem newItem)   {      //logic to convert new in old   } }  public class NewQuestionItem : OldQuestionItem {  } 

and use UploadTimeStamp as marker to understand, what Question is it.

Read More

Thursday, July 13, 2017

Add variable to nested list

Leave a Comment

Using R base, I would like to add a variable to a nested list where the variable changes for each nested list element. Below is an example. Thank you.

#CREATE EXAMPLE DATAFRAME DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"), DATE = seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day"))  #CREATE NESTED LIST DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF, as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t + i, "12", "31", sep = "-"))), ]))  #PRINT NESTED LIST lapply(DF, lapply, function(x) rbind(head(x), tail(x)))  #I WOULD LIKE TO SIMPLIFY THIS PART DF[[1]][[1]] <- within(DF[[1]][[1]], GROUP <- 2014) DF[[1]][[2]] <- within(DF[[1]][[2]], GROUP <- 2015)  DF[[2]][[1]] <- within(DF[[2]][[1]], GROUP <- 2014) DF[[2]][[2]] <- within(DF[[2]][[2]], GROUP <- 2015)  DF[[3]][[1]] <- within(DF[[3]][[1]], GROUP <- 2014) DF[[3]][[2]] <- within(DF[[3]][[2]], GROUP <- 2015)  #PRINT MODIFIED NESTED LIST lapply(DF, lapply, function(x) rbind(head(x), tail(x)))  #I AM SURPRISED THE FOLLOWING DOES NOT WORK DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"), DATE = seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day")) DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF, as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t + i, "12", "31", sep = "-"))),])) DF <- lapply(DF, function(x) lapply(2014:2015, function(t) within(x, GROUP <- t))) lapply(DF, lapply, function(x) rbind(head(x), tail(x))) 

6 Answers

Answers 1

This should do it

final_list<-list() for(i in seq(1, length(DF))){    new_list<-list()    for(j in seq(1,length(DF[[i]]))){      new_list[[j]]<-list(DF[[i]][[j]],GROUP=j)    }   final_list[[i]]<-new_list } 

Answers 2

#CREATE EXAMPLE DATAFRAME DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"), DATE = seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day"))  #CREATE NESTED LIST DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF, as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t + i, "12", "31", sep = "-"))), ]))  #PRINT NESTED LIST lapply(DF, lapply, function(x) rbind(head(x), tail(x)))  #I WOULD LIKE TO SIMPLIFY THIS PART DF[[1]][[1]] <- within(DF[[1]][[1]], GROUP <- 2014) DF[[1]][[2]] <- within(DF[[1]][[2]], GROUP <- 2015)  DF[[2]][[1]] <- within(DF[[2]][[1]], GROUP <- 2014) DF[[2]][[2]] <- within(DF[[2]][[2]], GROUP <- 2015)  DF[[3]][[1]] <- within(DF[[3]][[1]], GROUP <- 2014) DF[[3]][[2]] <- within(DF[[3]][[2]], GROUP <- 2015)  #PRINT MODIFIED NESTED LIST DF1 <- lapply(DF, lapply, function(x) rbind(head(x), tail(x))) DF1  #I AM SURPRISED THE FOLLOWING DOES NOT WORK DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"), DATE = seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day")) DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF, as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t + i, "12", "31", sep = "-"))),]))  GROUPS <- c(2014:2015)      DF <- lapply(DF, function(xs) lapply(1:2, function(t) within(xs[[t]], GROUP <- GROUPS[t])))  DF2 <- lapply(DF, lapply, function(x) rbind(head(x), tail(x))) DF2  all.equal(DF1, DF2) 

Answers 3

How about this? Had to use package dplyr since it makes adding a new variable to the dataframes easier with mutate_.

library(dplyr) # if not installed, install with install.packages("dplyr") DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"),  DATE = seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day"))  #CREATE NESTED LIST DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF,  as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t +  i, "12", "31", sep = "-"))), ]))  # loop over the first list with lapply and then loop over the nested lists  # and the desired GROUP values with mapply DF <- lapply(DF, function(x) mapply(FUN = function(df,number){mutate_(df,  "GROUP" = number)},x, 2014:2015, SIMPLIFY = F))  #PRINT NESTED LIST lapply(DF, lapply, function(x) rbind(head(x), tail(x))) 

As to why your way was not working: Think about what function(t) within(xs[[t]], GROUP <- GROUPS[t]) does. It does not return a dataframe.

Answers 4

This can be also achieved using Map in order to cbind the dates. i.e.,

lapply(DF, function(i) Map(cbind, i, c(2014, 2015)))  #or to set the name of that column to 'id',  lapply(DF, function(i) Map(function(x, y) cbind(x, id = y), i, c(2014, 2015))) 

However, If you don't mind flattening that list and keeping an extra id variable, you can use tidyverse to flatten and create the year id by graping the year from the first element of DATE from each data frame, i.e.

library(tidyverse)  new_df <- DF %>%    flatten_df(.id = 'list_id') %>%    group_by(list_id) %>%    mutate(id = sub('-.*', '', DATE[1]))  #which will give,  # A tibble: 19,725 x 4 # Groups:   list_id [6] #   list_id   NAME       DATE    id #     <chr> <fctr>     <date> <chr> # 1       1  FRANK 2014-01-01  2014 # 2       1   TONY 2014-01-01  2014 # 3       1     ED 2014-01-01  2014 # 4       1  FRANK 2014-01-02  2014 # 5       1   TONY 2014-01-02  2014 # 6       1     ED 2014-01-02  2014 # 7       1  FRANK 2014-01-03  2014 # 8       1   TONY 2014-01-03  2014 # 9       1     ED 2014-01-03  2014 #10       1  FRANK 2014-01-04  2014 # ... with 19,715 more rows 

Answers 5

I think the problem may the second lapply:

DF <- lapply(DF, function(x) lapply(2014:2015, function(t) within(x, GROUP <- t))) 

The lapply's don't appear to be extracting the desired component from the large list object. The first lapply iterates over the top level of the list, extracting a two-element list object, x, each time. The second lapply then iterates over a vector, providing a scalar vector, t, each time. So, the next part gets a two-element list (x) each time instead of the desired (unnamed) data frame.

Solution

If the object has already been created, you can iterate over the elements directly instead of indexing the list elements.

#CREATE NESTED LIST DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF, as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t + i, "12", "31", sep = "-"))), ]))  edit_level2 <- function(df) {     # figure out what the value of t should be based on the data.     t <- as.integer(format(min(df$DATE), "%Y"))     df$GROUP <- t     return(df) }  # iterate over the list object contents at *both* levels DF <- lapply(DF, function(level1) lapply(level1, function(level2) edit_level2(level2))) 

Note: this is similar to the solution presented by @Consistency in the comments--extracting the data frame is the issue.

Alternative

If you can you alter the code that is producing the list object, I suggest assigning the variable while creating the list object instead of modifying it afterwards (my original suggestion before edits).

#CREATE EXAMPLE DATAFRAME DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"),                    DATE = seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day"))  #CREATE NESTED LIST DF <- lapply(1:3, function(i) {      lapply(2014:2015, function(t) {          first <- as.Date(paste(t,     "01", "01", sep = "-"))          last  <- as.Date(paste(t + i, "12", "31", sep = "-"))           # create a local data frame         df <- DF[first <= DF$DATE & DF$DATE <= last, ]          # modify the local data frame         df$GROUP <- t          # return the modified data frame         df      })  }) 

Answers 6

OK, I think I've got it. At least all.equal returns TRUE, though identical does not. Here it goes. Note that I've changed your df names a bit.

#CREATE EXAMPLE DATAFRAME DF <- expand.grid(NAME = c("FRANK", "TONY", "ED"), DATE =    seq(as.Date("2014-01-01"), as.Date("2018-12-31"), by = "day"))  #CREATE NESTED LIST DF <- lapply(1:3, function(i) lapply(2014:2015, function(t) DF[with(DF, as.Date(paste(t, "01", "01", sep = "-")) <= DATE & DATE <= as.Date(paste(t + i, "12", "31", sep = "-"))), ]))  #PRINT NESTED LIST lapply(DF, lapply, function(x) rbind(head(x), tail(x)))  DF2 <- DF #I WOULD LIKE TO SIMPLIFY THIS PART DF2[[1]][[1]] <- within(DF2[[1]][[1]], GROUP <- 2014) DF2[[1]][[2]] <- within(DF2[[1]][[2]], GROUP <- 2015)  DF2[[2]][[1]] <- within(DF2[[2]][[1]], GROUP <- 2014) DF2[[2]][[2]] <- within(DF2[[2]][[2]], GROUP <- 2015)  DF2[[3]][[1]] <- within(DF2[[3]][[1]], GROUP <- 2014) DF2[[3]][[2]] <- within(DF2[[3]][[2]], GROUP <- 2015)  #PRINT MODIFIED NESTED LIST lapply(DF2, lapply, function(x) rbind(head(x), tail(x)))  ### New code DF3 <- DF DF3 <- lapply(DF3, function(x) {         lapply(2014:2015, function(t){             within(x[[t - 2013]], GROUP <- t)         })     }) identical(DF2, DF3) all.equal(DF2, DF3) 
Read More

Saturday, April 23, 2016

Prolog getting head and tail of string

Leave a Comment

I'm trying to wrap my brain around Prolog for the first time (SWI-Prolog) and I'm struggling with what I'm sure are the basics. I'm trying to take a string such as "pie" and print out the military NATO spelling of it to look something like this:

spellWord("Pie"). Papa India Echo 

Currently I'm just trying to verify that I'm using the [H|T] syntax and Write function correctly. My function is:

spellWord(String) :- String = [H|T], writeChar(H), spellWord(T).  writeChar(String) :- H == "P", print4("Papa"). 

When making a call to spellWord("Pie"). this currently just returns false.

3 Answers

Answers 1

SWI-Prolog has several different representation of what you might call "strings".

  • List of character codes (Unicode);
  • List of chars (one-letter atoms);
  • Strings, which are "atomic" objects, and can be manipulated only with the built-in predicates for strings;
  • And finally, of course, atoms.

You should read the documentation, but for now, you have at least two choices.

Choice 1: Use a flag to make double-quoted strings code lists

$ swipl --traditional Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.19-57-g9d8aa27) Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. Please visit http://www.swi-prolog.org for details.  For help, use ?- help(Topic). or ?- apropos(Word).  ?- X = "abc". X = [97, 98, 99]. 

At this point, your approach should work, as you now have a list.

Choice 2: Use the new code list syntax with back-ticks

?- X = `abc`. X = [97, 98, 99]. 

And, of course, there are predicates that convert between atoms, code lists, char lists, and strings. So, to make a list of chars (one-character atoms), you have:

  • atom_chars/2
  • char_code/2
  • string_chars/2

As for your predicate definition, consider using unification in the head. Also, don't mix side effects (printing) with what the predicate does. Let the top level (the Prolog interpreter) do the printing for you.

nato(p, 'Papa'). nato(i, 'India'). nato(e, 'Echo'). % and so on  word_nato([], []). word_nato([C|Cs], [N|Ns]) :-     char_code(Char, C),     char_type(U, to_lower(Char)),     nato(U, N),     word_nato(Cs, Ns). 

And with this:

?- word_nato(`Pie`, Nato). Nato = ['Papa', 'India', 'Echo']. 

I used chars (one-letter atoms) instead of character codes because those are easier to write.


And finally, you can use the following flag, and set_prolog_flag/2 at run time to change how Prolog treats a string enclosed in double quotes.

For example:

$ swipl Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.19-40-g2bcbced) Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. Please visit http://www.swi-prolog.org for details.  For help, use ?- help(Topic). or ?- apropos(Word).  ?- current_prolog_flag(double_quotes, DQs). DQs = string.  ?- string("foo"). true.  ?- set_prolog_flag(double_quotes, codes). true.  ?- X = "foo". X = [102, 111, 111].  ?- set_prolog_flag(double_quotes, chars). true.  ?- X = "foo". X = [f, o, o].  ?- set_prolog_flag(double_quotes, atom). true.  ?- X = "foo". X = foo. 

Answers 2

Regardless of the Prolog system you are using and unless you have to maintain existing code, stick to set_prolog_flag(double_quotes, chars). This works in many systems like B, GNU, IF, IV, Minerva, SICStus, SWI, YAP. So it is a safe bet. The other options mentioned by @Boris are hard to debug. One is even specific to SWI only.

?- set_prolog_flag(double_quotes, chars). true.  ?- L = "abc". L = [a, b, c]. 

With library(double_quotes) these strings can be printed more compactly.

In SWI, the best you can do is to put in your .swiplrc the lines:

:- set_prolog_flag(back_quotes, string). :- set_prolog_flag(double_quotes, chars). :- use_module(library(double_quotes)). 

For your concrete example, it is a good idea to avoid producing side-effects immmediately. Instead consider defining a relation between a word and the spelling:

word_spelling(Ws, Ys) :-    phrase(natospelling(Ws), Ys).  natospelling([]). natospelling([C|Cs]) -->    {char_lower(C, L)},    nato(L),    "\n",    natospelling(Cs).  nato(p) --> "Papa". nato(i) --> "India". nato(e) --> "Echo".  char_lower(C, L) :-    char_type(L, to_lower(C)).  ?- word_spelling("Pie",Xs). Xs = "Papa\nIndia\nEcho\n".  ?- word_spelling("Pie",Xs), format("~s",[Xs]). Papa India Echo Xs = "Papa\nIndia\nEcho\n". 

And here is your original definition. Most of the time, however, rather stick with the pure core of it.

spellWord(Ws) :-    word_spelling(Ws, Xs),    format("~s", [Xs]). 

Also note that SWI's built-in library(pio) only works for codes and leaves unnecessary choice-points open. Instead, use this replacement which works for chars and codes depending on the Prolog flag.

Historically, characters were first represented as atoms of length one. That is, 1972 in Prolog 0. However, there, strings were represented in a left-associative manner which facilitated suffix matching.

plur(nil-c-i-e-l, nil-c-i-e-u-x). 

Starting with Prolog I, 1973, double quotes meant a list of characters like today.

In 1977, DECsystem 10 Prolog changed the meaning of double quotes to lists of characters codes and used codes in place of chars. This made some I/O operations a little bit more efficient, but made debugging such programs much more difficult [76,105,107,101,32,116,104,105,115] - can you read it?.

ISO Prolog supports both. There is a flag double_quotes that indicates how double quotes are interpreted. Also, character related built-ins are present for both:

char_code/2  atom_chars/2, number_chars/2, get_char/1/2, peek_char/1/2, put_char/1/2  atom_codes/2, number_codes/2, get_code/1/2, peek_code/1/2, put_code/1/2 

Answers 3

The problems with your code are:

spellWord(String) :- String = [H|T], writeChar(H), spellWord(T). 

When you give this predicate a long string, it will invoke itself with the tail of that string. But when String is empty, it cannot be split into [H|T], therefore the predicate fails, returning false.

To fix this, you have to define additionally:

spellWord([]). 

This is the short form of:

spellWord(String) :- String = []. 

Your other predicate also has a problem:

writeChar(String) :- H == "P", print4("Papa"). 

You have two variables here, String and H. These variables are in no way related. So no matter what you pass as a parameter, it will not influence the H that you use for comparison. And since the == operator only does a comparison, without unification, writeChar fails at this point, returning false. This is the reason why there is no output at all.

Read More

Wednesday, March 9, 2016

how to rearrange an order of matches between two data frames

Leave a Comment

I have been busy with this question since last night and I could not figure out how to do it.

What I want to do is to match df1 strings to df2 strings and get the similar ones out

what I do is like this

# a function to arrange the data to have IDs for each string      normalize <- function(x, delim) {       x <- gsub(")", "", x, fixed=TRUE)       x <- gsub("(", "", x, fixed=TRUE)       idx <- rep(seq_len(length(x)), times=nchar(gsub(sprintf("[^%s]",delim), "", as.character(x)))+1)       names <- unlist(strsplit(as.character(x), delim))       return(setNames(idx, names))     }  # a function to arrange the second df   lookup <- normalize(df2[,1], ",")  # a function to match them and give the IDs process <- function(s) {   lookup_try <- lookup[names(s)]   found <- which(!is.na(lookup_try))   pos <- lookup_try[names(s)[found]]   return(paste(s[found], pos, sep="-"))   #change the last line to "return(as.character(pos))" to get only the result as in the comment } 

then I get the results like this

res <- lapply(colnames(df1), function(x) process(normalize(df1[,x], ";"))) 

This gives me the row number of each string from df1 and row number of string from df2 that matched. so the output of this data looks like this

> res $s1 [1] "3-4" "4-1" "5-4"  $s2 [1] "2-4"  "3-15" "7-16" 

The first column IDs is the row number of df2 which matched with strings in df1 The second column No is the number of times it matched The third column ID-col-n is the row number of string in df1 which matched with that string + their column name the forth is string from first column of the df1 which matched with that string the fifth column is the string of second column which matched with that string and so on

2 Answers

Answers 1

In this case I find it easier to switch the data to the wide format and before merging it to the lookup table.

You could try:

library(tidyr) library(dplyr) df1_tmp <- df1 df2_tmp <- df2 #add numerical id to df1_tmp to keep row information df1_tmp$id <- seq_along(df1_tmp[,1])  #switch to wide and unnest rows with several strings df1_tmp <- gather(df1_tmp,key="s_val",value="query_string",-id) df1_tmp <- df1_tmp %>%          mutate(query_string = strsplit(as.character(query_string), ";")) %>%          unnest(query_string)   df2_tmp$IDs. <- gsub("[()]", "", df2_tmp$IDs.)  #add numerical id to df1_tmp to keep row information df2_tmp$id <- seq_along(df2_tmp$IDs.)  #unnest rows with several strings df2_tmp <- df2_tmp %>%          mutate(IDs. = strsplit(as.character(IDs.), ",")) %>%          unnest(IDs.)  res <- merge(df1_tmp,df2_tmp,by.x="query_string",by.y="IDs.")  res$ID_col_n <- paste(paste0(res$id.x,res$s_val)) res$total_id <- 1:nrow(res) res <- spread(res,s_val,value=query_string,fill=NA) res #summarize to get required output   res <- res %>% group_by(id.y) %>%         mutate(No=n())  %>% group_by(id.y,No) %>%         summarise_each(funs(paste(.[!is.na(.)],collapse=","))) %>%          select(-id.x,-total_id)  colnames(res)[colnames(res)=="id.y"]<-"IDs"  res$df1_colMatch_counts <- rowSums(res[,-(1:3)]!="") df2_counts <- df2_tmp %>% group_by(id) %>% summarize(df2_string_counts=n()) res <- merge(res,df2_counts,by.x="IDs",by.y="id") res   res    IDs No    ID_col_n            s1     s2 df1_colMatch_counts df2_string_counts 1   1  1         4s1        P41182                          1                 2 2   2  1         4s1        P41182                          1                 2 3   3  1         4s1        P41182                          1                 2 4   4  3 2s2,3s1,5s1 Q9Y6Q9,Q09472 Q92831                   2                 4 5  15  1         3s2               P54612                   1                 5 6  16  1         7s2               O15143                   1                 7 

Answers 2

For this solution I use a general-purpose helper function meant to work around the limitation of rbind() that it can't handle inconsistent column names. You could probably use any function from Combine two data frames by rows (rbind) when they have different sets of columns, but I also wrote my own:

rbind.cn <- function(...,filler=NA) {     ## note: must explicitly set proper S3 class; otherwise, filler would corrupt the column type in cases where a column is missing from the first df     ## for example, logical NA (the default for filler) would nix factors, resulting in character after type promotion     ## to do this, will use single-row temp df as first argument to rbind()     ## note: tried zero-row to prevent need to excise afterward, but zero-row rbind() arguments are ignored for typing purposes     l <- list(...);     schema <- do.call(cbind,unname(lapply(l,function(df) df[1L,,drop=F]))); ## unname() is necessary, otherwise cbind() tries to be a good citizen and concats first df cell value found in lapply() names onto schema column names     schema <- schema[unique(names(schema))];     res <- do.call(rbind,c(list(schema),lapply(l,function(df) {         cns.add <- names(schema)[!names(schema)%in%names(df)];         do.call(cbind,c(             list(df),             setNames(rep(filler,length(cns.add)),cns.add),             stringsAsFactors=F         ));     })))[-1L,,drop=F];     ## fix up row names     rns <- do.call(c,lapply(l,rownames));     rownames(res) <- ifelse(grepl('^[0-9]+$',rns),seq_along(rns),rns);     res; }; 

I also wrote my own normalization function. I think you were on the right track by precomputing a normalized representation of both input data.frames, but because you used named vector indexing to match the ids, you didn't catch cases of duplicate names in df2, which is why your result is missing the additional occurrences of id P41182. Here's my normalization function:

## normalization function ## for each column, splits on sep and captures the id, row index, column index, and column name in a data.frame normalize <- function(df,sep) {     do.call(rbind,lapply(seq_along(df),function(ci) {         l <- strsplit(gsub('[()]','',df[[ci]]),sep);         cbind(             do.call(rbind,lapply(seq_along(l),function(ri)                 if (length(l[[ri]]) > 0L)                     data.frame(id=l[[ri]],ri=ri,stringsAsFactors=F)             )),             ci,             cn=names(df)[ci]         );     })); }; 

Here's the complete solution:

## normalize both data.frames df1.norm <- normalize(df1,';'); df2.norm <- normalize(df2,',');  ## join them on matching ids df.match <- merge(df1.norm,df2.norm,'id',suffixes=c('.1','.2')); df.match <- df.match[with(df.match,order(ri.2,cn.1,ri.1)),]; ## order by df2 row index, df1 column name, and finally df1 row index, as per required output  ## aggregate and format as required res <- do.call(rbind.cn,c(by(df.match,df.match$ri.2,function(x) {     strCols <- aggregate(id~cn.1,x[c('id','cn.1')],paste,collapse=','); ## conveniently, automatically orders by the grouping column cn.1     do.call(cbind,c(         list(data.frame(IDs=x$ri.2[1L],No=nrow(x),`ID-col-n`=paste0(x$ri.1,x$cn.1,collapse=','),stringsAsFactors=F,check.names=F)),         setNames(strCols$id,paste0('string-df1-',strCols$cn.1)),         stringsAsFactors=F     )); }),filler='-'));  ## order string-df1 columns res <- res[c(1:3,order(as.integer(sub('.*?([0-9]+)$','\\1',names(res)[-1:-3])))+3L)]; 

And here are all intermediate and final data.frames:

df1.norm; ##        id ri ci cn ## 1  Q9Y6W5  1  1 s1 ## 2  Q9Y6U3  2  1 s1 ## 3  Q9Y6Q9  3  1 s1 ## 4  P41182  4  1 s1 ## 5  Q9HCP0  4  1 s1 ## 6  Q09472  5  1 s1 ## 7  Q9Y6I3  6  1 s1 ## 8  Q9Y6H1  7  1 s1 ## 9  Q5T1J5  7  1 s1 ## 10 Q16835  1  2 s2 ## 11 P61809  2  2 s2 ## 12 Q92831  2  2 s2 ## 13 P41356  3  2 s2 ## 14 P54612  3  2 s2 ## 15 A41PH2  3  2 s2 ## 16 P3R117  4  2 s2 ## 17 P31908  5  2 s2 ## 18 P54112  6  2 s2 ## 19 O15143  7  2 s2 

df2.norm; ##        id ri ci   cn ## 1  P41182  1  1 IDs. ## 2  P56524  1  1 IDs. ## 3  P41182  2  1 IDs. ## 4  Q9UQL6  2  1 IDs. ## 5  P41182  3  1 IDs. ## 6  Q8WUI4  3  1 IDs. ## 7  Q92793  4  1 IDs. ## 8  Q09472  4  1 IDs. ## 9  Q9Y6Q9  4  1 IDs. ## 10 Q92831  4  1 IDs. ## 11 P30561  5  1 IDs. ## 12 P53762  5  1 IDs. ## 13 Q15021  6  1 IDs. ## 14 Q9BPX3  6  1 IDs. ## 15 Q15003  6  1 IDs. ## 16 O95347  6  1 IDs. ## 17 Q9NTJ3  6  1 IDs. ## 18 Q92902  7  1 IDs. ## 19 Q9NQG7  7  1 IDs. ## 20 Q969F9  8  1 IDs. ## 21 Q9UPZ3  8  1 IDs. ## 22 Q86YV9  8  1 IDs. ## 23 Q92903  9  1 IDs. ## 24 Q96NY9  9  1 IDs. ## 25 Q91VB4 10  1 IDs. ## 26 P59438 10  1 IDs. ## 27 Q8BLY7 10  1 IDs. ## 28 Q92828 11  1 IDs. ## 29 Q13227 11  1 IDs. ## 30 O15379 11  1 IDs. ## 31 O75376 11  1 IDs. ## 32 O60907 11  1 IDs. ## 33 Q9BZK7 11  1 IDs. ## 34 P78537 12  1 IDs. ## 35 Q6QNY1 12  1 IDs. ## 36 Q6QNY0 12  1 IDs. ## 37 Q9NUP1 12  1 IDs. ## 38 Q96EV8 12  1 IDs. ## 39 Q8TDH9 12  1 IDs. ## 40 Q9UL45 12  1 IDs. ## 41 O95295 12  1 IDs. ## 42 O55102 13  1 IDs. ## 43 Q9CWG9 13  1 IDs. ## 44 Q5U5M8 13  1 IDs. ## 45 Q8VED2 13  1 IDs. ## 46 Q91WZ8 13  1 IDs. ## 47 Q8R015 13  1 IDs. ## 48 Q9R0C0 13  1 IDs. ## 49 Q9Z266 13  1 IDs. ## 50 P30561 14  1 IDs. ## 51 O08915 14  1 IDs. ## 52 P07901 14  1 IDs. ## 53 P11499 14  1 IDs. ## 54 Q8WMR7 15  1 IDs. ## 55 P67776 15  1 IDs. ## 56 P11493 15  1 IDs. ## 57 P54612 15  1 IDs. ## 58 P54613 15  1 IDs. ## 59 P61160 16  1 IDs. ## 60 P61158 16  1 IDs. ## 61 O15143 16  1 IDs. ## 62 O15144 16  1 IDs. ## 63 O15145 16  1 IDs. ## 64 P59998 16  1 IDs. ## 65 O15511 16  1 IDs. 

df.match; ##       id ri.1 ci.1 cn.1 ri.2 ci.2 cn.2 ## 2 P41182    4    1   s1    1    1 IDs. ## 4 P41182    4    1   s1    2    1 IDs. ## 3 P41182    4    1   s1    3    1 IDs. ## 8 Q9Y6Q9    3    1   s1    4    1 IDs. ## 6 Q09472    5    1   s1    4    1 IDs. ## 7 Q92831    2    2   s2    4    1 IDs. ## 5 P54612    3    2   s2   15    1 IDs. ## 1 O15143    7    2   s2   16    1 IDs. 

res; ##   IDs No    ID-col-n string-df1-s1 string-df1-s2 ## 1   1  1         4s1        P41182             - ## 2   2  1         4s1        P41182             - ## 3   3  1         4s1        P41182             - ## 4   4  3 3s1,5s1,2s2 Q9Y6Q9,Q09472        Q92831 ## 5  15  1         3s2             -        P54612 ## 6  16  1         7s2             -        O15143 
Read More

Tuesday, March 8, 2016

Prolog - Finding adjacent elements in a list

Leave a Comment

I'm trying to define a predicate adjacent(X, Y, Zs) that is true if X and Y are adjacent in a list. My code is currently this:

adjacent(_, _, []). adjacent(X, Y, [X, Y|Tail]) :-   adjacent(X,Y, Tail). 

It works for the basic case of adjacent(c, d, [a, b, c, d, e]), but due to the base case, every other case returns true as well, and I'm stuck on that.

The other problem is that if X is not equal to the first part of the list's head, then it skips past both X and Y and goes to the next 'X'; e.g., if c isn't equal to a, then it skips past both a and b and checks if c is equal to c. This is problematic when, for example, the list is

[a, c, d, e] 

because it ends up never checking c (I believe).

I'm pretty lost on how to reconcile the two issues and turn my logical understanding of what needs to occur into code.

EDIT: Thanks to Christian Hujer's answer, my base case mistake has been corrected, so now I'm just stuck on the second issue.

SOLUTION: Thank you to everybody who answered, here is the non-built-in predicate solution to the problem.

adjacent(X, Y, [X, Y|_]). adjacent(X, Y, [_|Tail]) :-   adjacent(X, Y, Tail). 

Use of the built-in append/3 below.

adjacent(X, Y, Z) :-   append(_, [X,Y|_], Z). 

4 Answers

Answers 1

In the original solution attempt:

adjacent(_, _, []). adjacent(X, Y, [X, Y|Tail]) :-     adjacent(X,Y, Tail). 

As @ChristianHujer points out, the first clause should not be there because it isn't true. The empty list should have no adjacent elements.

The second clause is also problematic. It shows that X and Y are adjacent in the list, but then recurses and doesn't just succeed. A proper clause should be:

adjacent(X, Y, [X,Y|_]). 

Which says that X and Y are adjacent in the list if they're the first two elements in the list, regardless of what the tail is. This also forms a proper base case. Then your general, recursive clause should take care of the rest of the cases:

adjacent(X, Y, [_|Tail]) :-     adjacent(X, Y, Tail). 

This says that X and Y are adjacent in [_|Tail] if they're adjacent in Tail. This takes care of the second problem you were encountering.

Thus, the whole solution would be:

adjacent(X, Y, [X,Y|_]). adjacent(X, Y, [_|Tail]) :-     adjacent(X, Y, Tail). 

This will succeed as many times as X and Y appear together, in that order, in the list.


This is also naturally solvable with a DCG (although @repeat's append/3 based solution is more concise):

adjacent(X, Y) --> ..., [X, Y], ... . ... --> [] | [_], ... .  adjacent(X, Y, L) :- phrase(adjacent(X, Y), L). 

| ?- adjacent(b, c, [a,b,c,d]).  true ? a  (1 ms) no | ?-  

Answers 2

I think your base case is wrong. In your situation, you want recursion to terminate with a false predicate, not with a true predicate. And it's logical: In an empty list, there are no adjacent elements. Never.

Answers 3

In this answer we try to keep it simple—by building on append/3:

 adjacent(E0, E1, Es) :-     append(_, [E0,E1|_], Es). 

Sample query:

?- adjacent(X, Y, [a,b,c,d,e]). X = a, Y = b ; X = b, Y = c ; X = c, Y = d ; X = d, Y = e ; false. 

Answers 4

Challenge accepted!

 adjacent(X0, X1, [E0,E1|Es]) :-    adjacent_(Es, E0, E1, X0, X1).  adjacent_([],      E0, E1, E0, E1). adjacent_([E2|Es], E0, E1, X0, X1) :-    if_(E0 = X0,        E1 = X1,        adjacent_(Es, E1, E2, X0, X1)). 

Using SWI-Prolog we run:

 ?- set_prolog_flag(double_quotes, chars). true.  ?- adjacent(a, b, "abab"). true.  ?- adjacent(b, c, "abcd"). true.   ?- adjacent(X, Y, "abcd").    X = a, Y = b ;  X = b, Y = c ;  X = c, Y = d.  ?- adjacent(X, X, [A,B,C]).    X = A, A = B ;  X = B, B = C, dif(A, C). 
Read More

Monday, March 7, 2016

Check if a list is a palindrome. If not, insert elements to make it a palindrome. (Prolog)

Leave a Comment

I have written the following code to check whether it is a palindrome or not. I have also created the logic to insert elements when the list is not a palindrome

reverse_list(Inputlist, Outputlist) :-    reverse(Inputlist, [], Outputlist).      reverse([], Outputlist, Outputlist).     reverse([Head|Tail], List1, List2) :-    reverse(Tail, [Head|List1], List2).  printList([]). printList([X|List]) :-    write(X),    write(' '),    printList(List).  palindrome(List1) :-    reverse_list(List1, List2),    compareLists(List1, List1, List2, List2).  compareLists(L1, [], [], L2) :-    write("\nList is Palindrome").     compareLists(L1, [X|List1], [X|List2], L2) :-    compareLists(L1, List1, List2, L2),    !.         compareLists(L1, [X|List1], [Y|List2], [Z|L2]) :-    write("\nList is not Palindrome. "),    append(L1, L2, L),    printList(L). 

The code gives the correct output for

palindrome([a,b,c,a]). List is not Palindrome. a b c a c b a   palindrome([a,b,c]). List is not Palindrome. a b c b a  

However, for an input such as

palindrome([a,b,c,b]). List is not Palindrome. a b c b c b a  

The optimal solution however should be

 a b c b a 

What changes should I incorporate to be able to achieve this?

2 Answers

Answers 1

I think you need a predicate with two Args, In and Out :

pal([], []). pal([X], [X]). pal(In, Out) :-     % first we check if the first and last letter are the same     (   append([H|T], [H], In)         % we must check that the middle is a palindrome     ->  pal(T, T1),         append([H|T1], [H], Out)     ;   % if not, we remove the first letter         % and we work with the rest         In = [H|T],         % we compute the palindrome from T         pal(T,T1),         % and we complete the palindrome to         % fit the first letter of the input         append([H|T1], [H], Out)). 

EDIT1 This code looks good but there is a bug for

? pal([a,b,c,a], P). P = [a, b, c, b, a] . 

Should be [a,b,c,a,c,b,a] I'll try to fix it.

EDIT2 Looks correct :

build_pal([H|T], Out):-     pal(T,T1),     append([H|T1], [H], Out).   pal([], []). pal([X], [X]). pal(In, Out) :-     (   append([H|T], [H], In)     ->  pal(T, T1),         (   T = T1         ->  append([H|T1], [H], Out)         ;   build_pal(In, Out))     ;   build_pal(In, Out)). 

with output :

 ?- pal([a,b,c], P). P = [a, b, c, b, a] .   ?- pal([a,b,a], P). P = [a, b, a] .   ?- pal([a,b,c,b], P). P = [a, b, c, b, a] .   ?- pal([a,b,c,a], P). P = [a, b, c, a, c, b, a] .   ?- pal([a,b,a,c,a], P). P = [a, b, a, c, a, b, a] . 

Answers 2

The first 3 equations of a DCG capture the palindrome pattern. Add a fourth, covering the mismatch, to complete the specification:

p([]) --> []. p([T]) --> [T]. p([T|R]) --> [T], p(P), [T], {append(P,[T],R)}. p([T|R]) --> [T], p(P), {append(P,[T],R)}.  ?- phrase(p(L), [a,b,c,b]). L = [a, b, c, b, a] ; L = [a, b, c, c, b, a] ; L = [a, b, c, b, c, b, a] ; L = [a, b, c, b, b, c, b, a] ; false. 
Read More